Commit | Line | Data |
---|---|---|
5117d598 | 1 | <?php |
a2f10958 PS |
2 | // This file is part of Moodle - http://moodle.org/ |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
c72fe801 | 16 | |
17 | /** | |
c72fe801 | 18 | * Authentication Plugin: Moodle Network Authentication |
c72fe801 | 19 | * Multiple host authentication support for Moodle Network. |
20 | * | |
a2f10958 PS |
21 | * @package auth_mnet |
22 | * @author Martin Dougiamas | |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU Public License | |
c72fe801 | 24 | */ |
25 | ||
60d7078a | 26 | defined('MOODLE_INTERNAL') || die(); |
c72fe801 | 27 | |
6bc1e5d5 | 28 | require_once($CFG->libdir.'/authlib.php'); |
29 | ||
c72fe801 | 30 | /** |
31 | * Moodle Network authentication plugin. | |
32 | */ | |
6bc1e5d5 | 33 | class auth_plugin_mnet extends auth_plugin_base { |
c72fe801 | 34 | |
35 | /** | |
36 | * Constructor. | |
37 | */ | |
4a89e83b | 38 | public function __construct() { |
6bc1e5d5 | 39 | $this->authtype = 'mnet'; |
94cf0a1e | 40 | $this->config = get_config('auth_mnet'); |
287efec6 | 41 | $this->mnet = get_mnet_environment(); |
c72fe801 | 42 | } |
43 | ||
4a89e83b MG |
44 | /** |
45 | * Old syntax of class constructor. Deprecated in PHP7. | |
46 | * | |
47 | * @deprecated since Moodle 3.1 | |
48 | */ | |
49 | public function auth_plugin_mnet() { | |
50 | debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); | |
51 | self::__construct(); | |
52 | } | |
53 | ||
c72fe801 | 54 | /** |
55 | * This function is normally used to determine if the username and password | |
56 | * are correct for local logins. Always returns false, as local users do not | |
57 | * need to login over mnet xmlrpc. | |
58 | * | |
59 | * @param string $username The username | |
60 | * @param string $password The password | |
139ebfdb | 61 | * @return bool Authentication success or failure. |
c72fe801 | 62 | */ |
63 | function user_login($username, $password) { | |
3db241b3 | 64 | return false; // print_error("mnetlocal"); |
c72fe801 | 65 | } |
66 | ||
67 | /** | |
b584a358 | 68 | * Return user data for the provided token, compare with user_agent string. |
c72fe801 | 69 | * |
70 | * @param string $token The unique ID provided by remotehost. | |
052141ab | 71 | * @param string $useragent User Agent string. |
c72fe801 | 72 | * @return array $userdata Array of user info for remote host |
73 | */ | |
74 | function user_authorise($token, $useragent) { | |
287efec6 PL |
75 | global $CFG, $SITE, $DB; |
76 | $remoteclient = get_mnet_remote_client(); | |
de260e0f | 77 | require_once $CFG->dirroot . '/mnet/xmlrpc/serverlib.php'; |
c72fe801 | 78 | |
b584a358 | 79 | $mnet_session = $DB->get_record('mnet_session', array('token'=>$token, 'useragent'=>$useragent)); |
c72fe801 | 80 | if (empty($mnet_session)) { |
d234faf3 | 81 | throw new mnet_server_exception(1, 'authfail_nosessionexists'); |
c72fe801 | 82 | } |
83 | ||
84 | // check session confirm timeout | |
85 | if ($mnet_session->confirm_timeout < time()) { | |
d234faf3 | 86 | throw new mnet_server_exception(2, 'authfail_sessiontimedout'); |
c72fe801 | 87 | } |
88 | ||
89 | // session okay, try getting the user | |
7b0d12b2 | 90 | if (!$user = $DB->get_record('user', array('id'=>$mnet_session->userid))) { |
d234faf3 | 91 | throw new mnet_server_exception(3, 'authfail_usermismatch'); |
c72fe801 | 92 | } |
93 | ||
61506902 PL |
94 | $userdata = mnet_strip_user((array)$user, mnet_fields_to_send($remoteclient)); |
95 | ||
96 | // extra special ones | |
c72fe801 | 97 | $userdata['auth'] = 'mnet'; |
287efec6 | 98 | $userdata['wwwroot'] = $this->mnet->wwwroot; |
c72fe801 | 99 | $userdata['session.gc_maxlifetime'] = ini_get('session.gc_maxlifetime'); |
61506902 PL |
100 | |
101 | if (array_key_exists('picture', $userdata) && !empty($user->picture)) { | |
35d76df3 | 102 | $fs = get_file_storage(); |
bf0f06b1 | 103 | $usercontext = context_user::instance($user->id, MUST_EXIST); |
35d76df3 DM |
104 | if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) { |
105 | $userdata['_mnet_userpicture_timemodified'] = $usericonfile->get_timemodified(); | |
106 | $userdata['_mnet_userpicture_mimetype'] = $usericonfile->get_mimetype(); | |
107 | } else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) { | |
108 | $userdata['_mnet_userpicture_timemodified'] = $usericonfile->get_timemodified(); | |
109 | $userdata['_mnet_userpicture_mimetype'] = $usericonfile->get_mimetype(); | |
c72fe801 | 110 | } |
111 | } | |
4d8c087e | 112 | |
113 | $userdata['myhosts'] = array(); | |
df997f84 | 114 | if ($courses = enrol_get_users_courses($user->id, false)) { |
4d8c087e | 115 | $userdata['myhosts'][] = array('name'=> $SITE->shortname, 'url' => $CFG->wwwroot, 'count' => count($courses)); |
116 | } | |
117 | ||
152a2273 DM |
118 | $sql = "SELECT h.name AS hostname, h.wwwroot, h.id AS hostid, |
119 | COUNT(c.id) AS count | |
120 | FROM {mnetservice_enrol_courses} c | |
121 | JOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid) | |
122 | JOIN {mnet_host} h ON h.id = c.hostid | |
123 | WHERE e.userid = ? AND c.hostid = ? | |
124 | GROUP BY h.name, h.wwwroot, h.id"; | |
125 | ||
287efec6 | 126 | if ($courses = $DB->get_records_sql($sql, array($user->id, $remoteclient->id))) { |
4d8c087e | 127 | foreach($courses as $course) { |
128 | $userdata['myhosts'][] = array('name'=> $course->hostname, 'url' => $CFG->wwwroot.'/auth/mnet/jump.php?hostid='.$course->hostid, 'count' => $course->count); | |
129 | } | |
130 | } | |
131 | ||
c72fe801 | 132 | return $userdata; |
133 | } | |
134 | ||
135 | /** | |
136 | * Generate a random string for use as an RPC session token. | |
137 | */ | |
138 | function generate_token() { | |
139 | return sha1(str_shuffle('' . mt_rand() . time())); | |
140 | } | |
141 | ||
142 | /** | |
143 | * Starts an RPC jump session and returns the jump redirect URL. | |
d9be2106 | 144 | * |
145 | * @param int $mnethostid id of the mnet host to jump to | |
146 | * @param string $wantsurl url to redirect to after the jump (usually on remote system) | |
147 | * @param boolean $wantsurlbackhere defaults to false, means that the remote system should bounce us back here | |
148 | * rather than somewhere inside *its* wwwroot | |
c72fe801 | 149 | */ |
d9be2106 | 150 | function start_jump_session($mnethostid, $wantsurl, $wantsurlbackhere=false) { |
287efec6 | 151 | global $CFG, $USER, $DB; |
c72fe801 | 152 | require_once $CFG->dirroot . '/mnet/xmlrpc/client.php'; |
153 | ||
d79d5ac2 | 154 | if (\core\session\manager::is_loggedinas()) { |
10df8657 DM |
155 | print_error('notpermittedtojumpas', 'mnet'); |
156 | } | |
157 | ||
c72fe801 | 158 | // check remote login permissions |
0601e0ee | 159 | if (! has_capability('moodle/site:mnetlogintoremote', context_system::instance()) |
c72fe801 | 160 | or is_mnet_remote_user($USER) |
4f0c2d00 PS |
161 | or isguestuser() |
162 | or !isloggedin()) { | |
5a2a5331 | 163 | print_error('notpermittedtojump', 'mnet'); |
c72fe801 | 164 | } |
165 | ||
166 | // check for SSO publish permission first | |
167 | if ($this->has_service($mnethostid, 'sso_sp') == false) { | |
5a2a5331 | 168 | print_error('hostnotconfiguredforsso', 'mnet'); |
c72fe801 | 169 | } |
170 | ||
171 | // set RPC timeout to 30 seconds if not configured | |
c72fe801 | 172 | if (empty($this->config->rpc_negotiation_timeout)) { |
09c1b1a2 | 173 | $this->config->rpc_negotiation_timeout = 30; |
94cf0a1e | 174 | set_config('rpc_negotiation_timeout', '30', 'auth_mnet'); |
c72fe801 | 175 | } |
176 | ||
177 | // get the host info | |
178 | $mnet_peer = new mnet_peer(); | |
179 | $mnet_peer->set_id($mnethostid); | |
180 | ||
181 | // set up the session | |
7b0d12b2 | 182 | $mnet_session = $DB->get_record('mnet_session', |
183 | array('userid'=>$USER->id, 'mnethostid'=>$mnethostid, | |
184 | 'useragent'=>sha1($_SERVER['HTTP_USER_AGENT']))); | |
c72fe801 | 185 | if ($mnet_session == false) { |
1dffbae2 | 186 | $mnet_session = new stdClass(); |
c72fe801 | 187 | $mnet_session->mnethostid = $mnethostid; |
188 | $mnet_session->userid = $USER->id; | |
189 | $mnet_session->username = $USER->username; | |
190 | $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']); | |
191 | $mnet_session->token = $this->generate_token(); | |
192 | $mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout; | |
193 | $mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime'); | |
194 | $mnet_session->session_id = session_id(); | |
979425b5 | 195 | $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session); |
c72fe801 | 196 | } else { |
197 | $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']); | |
198 | $mnet_session->token = $this->generate_token(); | |
199 | $mnet_session->confirm_timeout = time() + $this->config->rpc_negotiation_timeout; | |
200 | $mnet_session->expires = time() + (integer)ini_get('session.gc_maxlifetime'); | |
201 | $mnet_session->session_id = session_id(); | |
979425b5 | 202 | $DB->update_record('mnet_session', $mnet_session); |
c72fe801 | 203 | } |
204 | ||
205 | // construct the redirection URL | |
206 | //$transport = mnet_get_protocol($mnet_peer->transport); | |
207 | $wantsurl = urlencode($wantsurl); | |
287efec6 | 208 | $url = "{$mnet_peer->wwwroot}{$mnet_peer->application->sso_land_url}?token={$mnet_session->token}&idp={$this->mnet->wwwroot}&wantsurl={$wantsurl}"; |
d9be2106 | 209 | if ($wantsurlbackhere) { |
210 | $url .= '&remoteurl=1'; | |
211 | } | |
c72fe801 | 212 | |
213 | return $url; | |
214 | } | |
215 | ||
216 | /** | |
217 | * This function confirms the remote (ID provider) host's mnet session | |
218 | * by communicating the token and UA over the XMLRPC transport layer, and | |
219 | * returns the local user record on success. | |
220 | * | |
2078b2a3 PL |
221 | * @param string $token The random session token. |
222 | * @param mnet_peer $remotepeer The ID provider mnet_peer object. | |
139ebfdb | 223 | * @return array The local user record. |
c72fe801 | 224 | */ |
2078b2a3 | 225 | function confirm_mnet_session($token, $remotepeer) { |
287efec6 | 226 | global $CFG, $DB; |
c72fe801 | 227 | require_once $CFG->dirroot . '/mnet/xmlrpc/client.php'; |
35d76df3 | 228 | require_once $CFG->libdir . '/gdlib.php'; |
bb78e249 | 229 | require_once($CFG->dirroot.'/user/lib.php'); |
c72fe801 | 230 | |
231 | // verify the remote host is configured locally before attempting RPC call | |
2078b2a3 | 232 | if (! $remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotepeer->wwwroot, 'deleted' => 0))) { |
5a2a5331 | 233 | print_error('notpermittedtoland', 'mnet'); |
c72fe801 | 234 | } |
235 | ||
c72fe801 | 236 | // set up the RPC request |
237 | $mnetrequest = new mnet_xmlrpc_client(); | |
238 | $mnetrequest->set_method('auth/mnet/auth.php/user_authorise'); | |
239 | ||
240 | // set $token and $useragent parameters | |
241 | $mnetrequest->add_param($token); | |
242 | $mnetrequest->add_param(sha1($_SERVER['HTTP_USER_AGENT'])); | |
243 | ||
244 | // Thunderbirds are go! Do RPC call and store response | |
245 | if ($mnetrequest->send($remotepeer) === true) { | |
246 | $remoteuser = (object) $mnetrequest->response; | |
247 | } else { | |
016bac47 | 248 | foreach ($mnetrequest->error as $errormessage) { |
249 | list($code, $message) = array_map('trim',explode(':', $errormessage, 2)); | |
250 | if($code == 702) { | |
251 | $site = get_site(); | |
2078b2a3 | 252 | print_error('mnet_session_prohibited', 'mnet', $remotepeer->wwwroot, format_string($site->fullname)); |
016bac47 | 253 | exit; |
254 | } | |
71502268 | 255 | $message .= "ERROR $code:<br/>$errormessage<br/>"; |
c72fe801 | 256 | } |
e2490433 | 257 | print_error("rpcerror", '', '', $message); |
c72fe801 | 258 | } |
15e47723 | 259 | unset($mnetrequest); |
c72fe801 | 260 | |
261 | if (empty($remoteuser) or empty($remoteuser->username)) { | |
016bac47 | 262 | print_error('unknownerror', 'mnet'); |
263 | exit; | |
c72fe801 | 264 | } |
265 | ||
8df850ad | 266 | if (user_not_fully_set_up($remoteuser, false)) { |
aa802711 PL |
267 | print_error('notenoughidpinfo', 'mnet'); |
268 | exit; | |
269 | } | |
270 | ||
61506902 PL |
271 | $remoteuser = mnet_strip_user($remoteuser, mnet_fields_to_import($remotepeer)); |
272 | ||
273 | $remoteuser->auth = 'mnet'; | |
274 | $remoteuser->wwwroot = $remotepeer->wwwroot; | |
275 | ||
67c1d98d DM |
276 | // the user may roam from Moodle 1.x where lang has _utf8 suffix |
277 | // also, make sure that the lang is actually installed, otherwise set site default | |
bf36ee9b DM |
278 | if (isset($remoteuser->lang)) { |
279 | $remoteuser->lang = clean_param(str_replace('_utf8', '', $remoteuser->lang), PARAM_LANG); | |
280 | } | |
67c1d98d DM |
281 | if (empty($remoteuser->lang)) { |
282 | if (!empty($CFG->lang)) { | |
283 | $remoteuser->lang = $CFG->lang; | |
284 | } else { | |
285 | $remoteuser->lang = 'en'; | |
286 | } | |
287 | } | |
15e47723 | 288 | $firsttime = false; |
289 | ||
c72fe801 | 290 | // get the local record for the remote user |
7b0d12b2 | 291 | $localuser = $DB->get_record('user', array('username'=>$remoteuser->username, 'mnethostid'=>$remotehost->id)); |
c72fe801 | 292 | |
293 | // add the remote user to the database if necessary, and if allowed | |
294 | // TODO: refactor into a separate function | |
32d651c8 | 295 | if (empty($localuser) || ! $localuser->id) { |
4a3c3308 | 296 | /* |
c72fe801 | 297 | if (empty($this->config->auto_add_remote_users)) { |
5a2a5331 | 298 | print_error('nolocaluser', 'mnet'); |
4a3c3308 PL |
299 | } See MDL-21327 for why this is commented out |
300 | */ | |
c72fe801 | 301 | $remoteuser->mnethostid = $remotehost->id; |
f720c2c0 | 302 | $remoteuser->firstaccess = 0; |
a014e3bc | 303 | $remoteuser->confirmed = 1; |
07ed083e | 304 | |
3ce68e4f | 305 | $remoteuser->id = user_create_user($remoteuser, false); |
15e47723 | 306 | $firsttime = true; |
98dbda95 | 307 | $localuser = $remoteuser; |
c72fe801 | 308 | } |
309 | ||
310 | // check sso access control list for permission first | |
311 | if (!$this->can_login_remotely($localuser->username, $remotehost->id)) { | |
c94fc98c | 312 | print_error('sso_mnet_login_refused', 'mnet', '', array('user'=>$localuser->username, 'host'=>$remotehost->name)); |
c72fe801 | 313 | } |
314 | ||
35d76df3 DM |
315 | $fs = get_file_storage(); |
316 | ||
c72fe801 | 317 | // update the local user record with remote user data |
318 | foreach ((array) $remoteuser as $key => $val) { | |
c72fe801 | 319 | |
35d76df3 DM |
320 | if ($key == '_mnet_userpicture_timemodified' and empty($CFG->disableuserimages) and isset($remoteuser->picture)) { |
321 | // update the user picture if there is a newer verion at the identity provider | |
bf0f06b1 | 322 | $usercontext = context_user::instance($localuser->id, MUST_EXIST); |
35d76df3 DM |
323 | if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) { |
324 | $localtimemodified = $usericonfile->get_timemodified(); | |
325 | } else if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) { | |
326 | $localtimemodified = $usericonfile->get_timemodified(); | |
327 | } else { | |
328 | $localtimemodified = 0; | |
c72fe801 | 329 | } |
330 | ||
35d76df3 DM |
331 | if (!empty($val) and $localtimemodified < $val) { |
332 | mnet_debug('refetching the user picture from the identity provider host'); | |
c72fe801 | 333 | $fetchrequest = new mnet_xmlrpc_client(); |
334 | $fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image'); | |
335 | $fetchrequest->add_param($localuser->username); | |
336 | if ($fetchrequest->send($remotepeer) === true) { | |
337 | if (strlen($fetchrequest->response['f1']) > 0) { | |
7aa06e6d | 338 | $imagefilename = $CFG->tempdir . '/mnet-usericon-' . $localuser->id; |
c72fe801 | 339 | $imagecontents = base64_decode($fetchrequest->response['f1']); |
35d76df3 | 340 | file_put_contents($imagefilename, $imagecontents); |
4d254790 PS |
341 | if ($newrev = process_new_icon($usercontext, 'user', 'icon', 0, $imagefilename)) { |
342 | $localuser->picture = $newrev; | |
35d76df3 DM |
343 | } |
344 | unlink($imagefilename); | |
c72fe801 | 345 | } |
35d76df3 DM |
346 | // note that since Moodle 2.0 we ignore $fetchrequest->response['f2'] |
347 | // the mimetype information provided is ignored and the type of the file is detected | |
348 | // by process_new_icon() | |
c72fe801 | 349 | } |
350 | } | |
351 | } | |
352 | ||
4d8c087e | 353 | if($key == 'myhosts') { |
0743661e | 354 | $localuser->mnet_foreign_host_array = array(); |
15e47723 | 355 | foreach($val as $rhost) { |
356 | $name = clean_param($rhost['name'], PARAM_ALPHANUM); | |
357 | $url = clean_param($rhost['url'], PARAM_URL); | |
358 | $count = clean_param($rhost['count'], PARAM_INT); | |
4d8c087e | 359 | $url_is_local = stristr($url , $CFG->wwwroot); |
360 | if (!empty($name) && !empty($count) && empty($url_is_local)) { | |
139ebfdb | 361 | $localuser->mnet_foreign_host_array[] = array('name' => $name, |
362 | 'url' => $url, | |
0743661e | 363 | 'count' => $count); |
4d8c087e | 364 | } |
365 | } | |
366 | } | |
367 | ||
c72fe801 | 368 | $localuser->{$key} = $val; |
369 | } | |
370 | ||
371 | $localuser->mnethostid = $remotepeer->id; | |
bb78e249 | 372 | user_update_user($localuser, false); |
c72fe801 | 373 | |
15e47723 | 374 | if (!$firsttime) { |
375 | // repeat customer! let the IDP know about enrolments | |
139ebfdb | 376 | // we have for this user. |
15e47723 | 377 | // set up the RPC request |
378 | $mnetrequest = new mnet_xmlrpc_client(); | |
379 | $mnetrequest->set_method('auth/mnet/auth.php/update_enrolments'); | |
380 | ||
381 | // pass username and an assoc array of "my courses" | |
152a2273 | 382 | // with info so that the IDP can maintain mnetservice_enrol_enrolments |
15e47723 | 383 | $mnetrequest->add_param($remoteuser->username); |
df997f84 PS |
384 | $fields = 'id, category, sortorder, fullname, shortname, idnumber, summary, startdate, visible'; |
385 | $courses = enrol_get_users_courses($localuser->id, false, $fields, 'visible DESC,sortorder ASC'); | |
15e47723 | 386 | if (is_array($courses) && !empty($courses)) { |
387 | // Second request to do the JOINs that we'd have done | |
df997f84 | 388 | // inside enrol_get_users_courses() if we had been allowed |
139ebfdb | 389 | $sql = "SELECT c.id, |
df997f84 | 390 | cc.name AS cat_name, cc.description AS cat_description |
7b0d12b2 | 391 | FROM {course} c |
392 | JOIN {course_categories} cc ON c.category = cc.id | |
7b0d12b2 | 393 | WHERE c.id IN (" . join(',',array_keys($courses)) . ')'; |
394 | $extra = $DB->get_records_sql($sql); | |
15e47723 | 395 | |
396 | $keys = array_keys($courses); | |
93288f22 | 397 | $studentroles = get_archetype_roles('student'); |
6f5bc058 RT |
398 | if (!empty($studentroles)) { |
399 | $defaultrole = reset($studentroles); | |
400 | //$defaultrole = get_default_course_role($ccache[$shortname]); //TODO: rewrite this completely, there is no default course role any more!!! | |
401 | foreach ($keys AS $id) { | |
402 | if ($courses[$id]->visible == 0) { | |
403 | unset($courses[$id]); | |
404 | continue; | |
405 | } | |
406 | $courses[$id]->cat_id = $courses[$id]->category; | |
407 | $courses[$id]->defaultroleid = $defaultrole->id; | |
408 | unset($courses[$id]->category); | |
409 | unset($courses[$id]->visible); | |
410 | ||
411 | $courses[$id]->cat_name = $extra[$id]->cat_name; | |
412 | $courses[$id]->cat_description = $extra[$id]->cat_description; | |
413 | $courses[$id]->defaultrolename = $defaultrole->name; | |
414 | // coerce to array | |
415 | $courses[$id] = (array)$courses[$id]; | |
573f8b02 | 416 | } |
6f5bc058 RT |
417 | } else { |
418 | throw new moodle_exception('unknownrole', 'error', '', 'student'); | |
15e47723 | 419 | } |
62d78bf5 | 420 | } else { |
421 | // if the array is empty, send it anyway | |
422 | // we may be clearing out stale entries | |
139ebfdb | 423 | $courses = array(); |
62d78bf5 | 424 | } |
425 | $mnetrequest->add_param($courses); | |
15e47723 | 426 | |
62d78bf5 | 427 | // Call 0800-RPC Now! -- we don't care too much if it fails |
428 | // as it's just informational. | |
429 | if ($mnetrequest->send($remotepeer) === false) { | |
430 | // error_log(print_r($mnetrequest->error,1)); | |
15e47723 | 431 | } |
432 | } | |
433 | ||
c72fe801 | 434 | return $localuser; |
435 | } | |
436 | ||
2078b2a3 PL |
437 | |
438 | /** | |
439 | * creates (or updates) the mnet session once | |
440 | * {@see confirm_mnet_session} and {@see complete_user_login} have both been called | |
441 | * | |
442 | * @param stdclass $user the local user (must exist already | |
443 | * @param string $token the jump/land token | |
444 | * @param mnet_peer $remotepeer the mnet_peer object of this users's idp | |
445 | */ | |
446 | public function update_mnet_session($user, $token, $remotepeer) { | |
447 | global $DB; | |
448 | $session_gc_maxlifetime = 1440; | |
449 | if (isset($user->session_gc_maxlifetime)) { | |
450 | $session_gc_maxlifetime = $user->session_gc_maxlifetime; | |
451 | } | |
452 | if (!$mnet_session = $DB->get_record('mnet_session', | |
453 | array('userid'=>$user->id, 'mnethostid'=>$remotepeer->id, | |
454 | 'useragent'=>sha1($_SERVER['HTTP_USER_AGENT'])))) { | |
1dffbae2 | 455 | $mnet_session = new stdClass(); |
2078b2a3 PL |
456 | $mnet_session->mnethostid = $remotepeer->id; |
457 | $mnet_session->userid = $user->id; | |
458 | $mnet_session->username = $user->username; | |
459 | $mnet_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']); | |
460 | $mnet_session->token = $token; // Needed to support simultaneous sessions | |
461 | // and preserving DB rec uniqueness | |
462 | $mnet_session->confirm_timeout = time(); | |
463 | $mnet_session->expires = time() + (integer)$session_gc_maxlifetime; | |
464 | $mnet_session->session_id = session_id(); | |
465 | $mnet_session->id = $DB->insert_record('mnet_session', $mnet_session); | |
466 | } else { | |
467 | $mnet_session->expires = time() + (integer)$session_gc_maxlifetime; | |
468 | $DB->update_record('mnet_session', $mnet_session); | |
469 | } | |
470 | } | |
471 | ||
472 | ||
473 | ||
62d78bf5 | 474 | /** |
475 | * Invoke this function _on_ the IDP to update it with enrolment info local to | |
476 | * the SP right after calling user_authorise() | |
477 | * | |
de260e0f | 478 | * Normally called by the SP after calling user_authorise() |
62d78bf5 | 479 | * |
de260e0f | 480 | * @param string $username The username |
5c7bc383 | 481 | * @param array $courses Assoc array of courses following the structure of mnetservice_enrol_courses |
de260e0f | 482 | * @return bool |
62d78bf5 | 483 | */ |
484 | function update_enrolments($username, $courses) { | |
287efec6 PL |
485 | global $CFG, $DB; |
486 | $remoteclient = get_mnet_remote_client(); | |
62d78bf5 | 487 | |
488 | if (empty($username) || !is_array($courses)) { | |
489 | return false; | |
490 | } | |
491 | // make sure it is a user we have an in active session | |
492 | // with that host... | |
ee4cd8f1 DM |
493 | $mnetsessions = $DB->get_records('mnet_session', array('username' => $username, 'mnethostid' => $remoteclient->id), '', 'id, userid'); |
494 | $userid = null; | |
495 | foreach ($mnetsessions as $mnetsession) { | |
496 | if (is_null($userid)) { | |
497 | $userid = $mnetsession->userid; | |
498 | continue; | |
499 | } | |
500 | if ($userid != $mnetsession->userid) { | |
501 | throw new mnet_server_exception(3, 'authfail_usermismatch'); | |
502 | } | |
62d78bf5 | 503 | } |
504 | ||
505 | if (empty($courses)) { // no courses? clear out quickly | |
152a2273 | 506 | $DB->delete_records('mnetservice_enrol_enrolments', array('hostid'=>$remoteclient->id, 'userid'=>$userid)); |
62d78bf5 | 507 | return true; |
508 | } | |
509 | ||
573f8b02 | 510 | // IMPORTANT: Ask for remoteid as the first element in the query, so |
511 | // that the array that comes back is indexed on the same field as the | |
512 | // array that we have received from the remote client | |
152a2273 DM |
513 | $sql = "SELECT c.remoteid, c.id, c.categoryid AS cat_id, c.categoryname AS cat_name, c.sortorder, |
514 | c.fullname, c.shortname, c.idnumber, c.summary, c.summaryformat, c.startdate, | |
515 | e.id AS enrolmentid | |
516 | FROM {mnetservice_enrol_courses} c | |
517 | LEFT JOIN {mnetservice_enrol_enrolments} e ON (e.hostid = c.hostid AND e.remotecourseid = c.remoteid) | |
518 | WHERE e.userid = ? AND c.hostid = ?"; | |
573f8b02 | 519 | |
287efec6 | 520 | $currentcourses = $DB->get_records_sql($sql, array($userid, $remoteclient->id)); |
573f8b02 | 521 | |
522 | $local_courseid_array = array(); | |
152a2273 | 523 | foreach($courses as $ix => $course) { |
573f8b02 | 524 | |
525 | $course['remoteid'] = $course['id']; | |
287efec6 | 526 | $course['hostid'] = (int)$remoteclient->id; |
573f8b02 | 527 | $userisregd = false; |
528 | ||
152a2273 DM |
529 | // if we do not have the the information about the remote course, it is not available |
530 | // to us for remote enrolment - skip | |
531 | if (array_key_exists($course['remoteid'], $currentcourses)) { | |
573f8b02 | 532 | // Pointer to current course: |
533 | $currentcourse =& $currentcourses[$course['remoteid']]; | |
534 | // We have a record - is it up-to-date? | |
535 | $course['id'] = $currentcourse->id; | |
536 | ||
537 | $saveflag = false; | |
538 | ||
539 | foreach($course as $key => $value) { | |
540 | if ($currentcourse->$key != $value) { | |
541 | $saveflag = true; | |
542 | $currentcourse->$key = $value; | |
543 | } | |
544 | } | |
545 | ||
546 | if ($saveflag) { | |
152a2273 | 547 | $DB->update_record('mnetervice_enrol_courses', $currentcourse); |
573f8b02 | 548 | } |
139ebfdb | 549 | |
152a2273 | 550 | if (isset($currentcourse->enrolmentid) && is_numeric($currentcourse->enrolmentid)) { |
573f8b02 | 551 | $userisregd = true; |
552 | } | |
152a2273 DM |
553 | } else { |
554 | unset ($courses[$ix]); | |
555 | continue; | |
573f8b02 | 556 | } |
557 | ||
558 | // By this point, we should always have a $dataObj->id | |
559 | $local_courseid_array[] = $course['id']; | |
560 | ||
561 | // Do we have a record for this assignment? | |
562 | if ($userisregd) { | |
563 | // Yes - we know about this one already | |
564 | // We don't want to do updates because the new data is probably | |
565 | // 'less complete' than the data we have. | |
566 | } else { | |
567 | // No - create a record | |
568 | $assignObj = new stdClass(); | |
569 | $assignObj->userid = $userid; | |
287efec6 | 570 | $assignObj->hostid = (int)$remoteclient->id; |
152a2273 | 571 | $assignObj->remotecourseid = $course['remoteid']; |
573f8b02 | 572 | $assignObj->rolename = $course['defaultrolename']; |
54f69781 | 573 | $assignObj->id = $DB->insert_record('mnetservice_enrol_enrolments', $assignObj); |
573f8b02 | 574 | } |
575 | } | |
62d78bf5 | 576 | |
573f8b02 | 577 | // Clean up courses that the user is no longer enrolled in. |
42ae4ff2 DM |
578 | if (!empty($local_courseid_array)) { |
579 | $local_courseid_string = implode(', ', $local_courseid_array); | |
580 | $whereclause = " userid = ? AND hostid = ? AND remotecourseid NOT IN ($local_courseid_string)"; | |
581 | $DB->delete_records_select('mnetservice_enrol_enrolments', $whereclause, array($userid, $remoteclient->id)); | |
582 | } | |
62d78bf5 | 583 | } |
584 | ||
edb5da83 PS |
585 | function prevent_local_passwords() { |
586 | return true; | |
587 | } | |
588 | ||
c72fe801 | 589 | /** |
590 | * Returns true if this authentication plugin is 'internal'. | |
591 | * | |
139ebfdb | 592 | * @return bool |
c72fe801 | 593 | */ |
594 | function is_internal() { | |
595 | return false; | |
596 | } | |
597 | ||
598 | /** | |
599 | * Returns true if this authentication plugin can change the user's | |
600 | * password. | |
601 | * | |
139ebfdb | 602 | * @return bool |
c72fe801 | 603 | */ |
604 | function can_change_password() { | |
430759a5 | 605 | //TODO: it should be able to redirect, right? |
c72fe801 | 606 | return false; |
607 | } | |
608 | ||
609 | /** | |
610 | * Returns the URL for changing the user's pw, or false if the default can | |
611 | * be used. | |
612 | * | |
99f9f85f | 613 | * @return moodle_url |
c72fe801 | 614 | */ |
615 | function change_password_url() { | |
99f9f85f | 616 | return null; |
c72fe801 | 617 | } |
618 | ||
619 | /** | |
620 | * Prints a form for configuring this authentication plugin. | |
621 | * | |
622 | * This function is called from admin/auth.php, and outputs a full page with | |
623 | * a form for configuring this plugin. | |
624 | * | |
de260e0f PL |
625 | * @param object $config |
626 | * @param object $err | |
627 | * @param array $user_fields | |
c72fe801 | 628 | */ |
139ebfdb | 629 | function config_form($config, $err, $user_fields) { |
7b0d12b2 | 630 | global $CFG, $DB; |
14518364 | 631 | |
632 | $query = " | |
633 | SELECT | |
634 | h.id, | |
635 | h.name as hostname, | |
636 | h.wwwroot, | |
637 | h2idp.publish as idppublish, | |
638 | h2idp.subscribe as idpsubscribe, | |
639 | idp.name as idpname, | |
640 | h2sp.publish as sppublish, | |
641 | h2sp.subscribe as spsubscribe, | |
642 | sp.name as spname | |
643 | FROM | |
7b0d12b2 | 644 | {mnet_host} h |
14518364 | 645 | LEFT JOIN |
7b0d12b2 | 646 | {mnet_host2service} h2idp |
14518364 | 647 | ON |
648 | (h.id = h2idp.hostid AND | |
649 | (h2idp.publish = 1 OR | |
650 | h2idp.subscribe = 1)) | |
651 | INNER JOIN | |
7b0d12b2 | 652 | {mnet_service} idp |
14518364 | 653 | ON |
654 | (h2idp.serviceid = idp.id AND | |
655 | idp.name = 'sso_idp') | |
656 | LEFT JOIN | |
7b0d12b2 | 657 | {mnet_host2service} h2sp |
14518364 | 658 | ON |
659 | (h.id = h2sp.hostid AND | |
660 | (h2sp.publish = 1 OR | |
661 | h2sp.subscribe = 1)) | |
662 | INNER JOIN | |
7b0d12b2 | 663 | {mnet_service} sp |
14518364 | 664 | ON |
665 | (h2sp.serviceid = sp.id AND | |
666 | sp.name = 'sso_sp') | |
667 | WHERE | |
668 | ((h2idp.publish = 1 AND h2sp.subscribe = 1) OR | |
669 | (h2sp.publish = 1 AND h2idp.subscribe = 1)) AND | |
7b0d12b2 | 670 | h.id != ? |
14518364 | 671 | ORDER BY |
672 | h.name ASC"; | |
673 | ||
14518364 | 674 | $id_providers = array(); |
675 | $service_providers = array(); | |
7b0d12b2 | 676 | if ($resultset = $DB->get_records_sql($query, array($CFG->mnet_localhost_id))) { |
d525ca25 | 677 | foreach($resultset as $hostservice) { |
678 | if(!empty($hostservice->idppublish) && !empty($hostservice->spsubscribe)) { | |
679 | $service_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot); | |
680 | } | |
681 | if(!empty($hostservice->idpsubscribe) && !empty($hostservice->sppublish)) { | |
682 | $id_providers[]= array('id' => $hostservice->id, 'name' => $hostservice->hostname, 'wwwroot' => $hostservice->wwwroot); | |
683 | } | |
14518364 | 684 | } |
685 | } | |
139ebfdb | 686 | |
c72fe801 | 687 | include "config.html"; |
688 | } | |
689 | ||
690 | /** | |
691 | * Processes and stores configuration data for this authentication plugin. | |
692 | */ | |
693 | function process_config($config) { | |
694 | // set to defaults if undefined | |
695 | if (!isset ($config->rpc_negotiation_timeout)) { | |
5671e77f | 696 | $config->rpc_negotiation_timeout = '30'; |
c72fe801 | 697 | } |
4a3c3308 | 698 | /* |
c72fe801 | 699 | if (!isset ($config->auto_add_remote_users)) { |
700 | $config->auto_add_remote_users = '0'; | |
4a3c3308 | 701 | } See MDL-21327 for why this is commented out |
94cf0a1e | 702 | set_config('auto_add_remote_users', $config->auto_add_remote_users, 'auth_mnet'); |
4a3c3308 | 703 | */ |
c72fe801 | 704 | |
705 | // save settings | |
94cf0a1e | 706 | set_config('rpc_negotiation_timeout', $config->rpc_negotiation_timeout, 'auth_mnet'); |
c72fe801 | 707 | |
708 | return true; | |
709 | } | |
710 | ||
711 | /** | |
712 | * Poll the IdP server to let it know that a user it has authenticated is still | |
713 | * online | |
714 | * | |
715 | * @return void | |
716 | */ | |
717 | function keepalive_client() { | |
287efec6 | 718 | global $CFG, $DB; |
c72fe801 | 719 | $cutoff = time() - 300; // TODO - find out what the remote server's session |
720 | // cutoff is, and preempt that | |
721 | ||
722 | $sql = " | |
723 | select | |
724 | id, | |
725 | username, | |
726 | mnethostid | |
727 | from | |
7b0d12b2 | 728 | {user} |
c72fe801 | 729 | where |
7b0d12b2 | 730 | lastaccess > ? AND |
731 | mnethostid != ? | |
c72fe801 | 732 | order by |
733 | mnethostid"; | |
734 | ||
7b0d12b2 | 735 | $immigrants = $DB->get_records_sql($sql, array($cutoff, $CFG->mnet_localhost_id)); |
c72fe801 | 736 | |
737 | if ($immigrants == false) { | |
738 | return true; | |
739 | } | |
740 | ||
741 | $usersArray = array(); | |
742 | foreach($immigrants as $immigrant) { | |
743 | $usersArray[$immigrant->mnethostid][] = $immigrant->username; | |
744 | } | |
745 | ||
746 | require_once $CFG->dirroot . '/mnet/xmlrpc/client.php'; | |
747 | foreach($usersArray as $mnethostid => $users) { | |
748 | $mnet_peer = new mnet_peer(); | |
749 | $mnet_peer->set_id($mnethostid); | |
750 | ||
751 | $mnet_request = new mnet_xmlrpc_client(); | |
752 | $mnet_request->set_method('auth/mnet/auth.php/keepalive_server'); | |
753 | ||
754 | // set $token and $useragent parameters | |
755 | $mnet_request->add_param($users); | |
756 | ||
757 | if ($mnet_request->send($mnet_peer) === true) { | |
758 | if (!isset($mnet_request->response['code'])) { | |
759 | debugging("Server side error has occured on host $mnethostid"); | |
760 | continue; | |
761 | } elseif ($mnet_request->response['code'] > 0) { | |
762 | debugging($mnet_request->response['message']); | |
763 | } | |
139ebfdb | 764 | |
c72fe801 | 765 | if (!isset($mnet_request->response['last log id'])) { |
766 | debugging("Server side error has occured on host $mnethostid\nNo log ID was received."); | |
767 | continue; | |
768 | } | |
769 | } else { | |
139ebfdb | 770 | debugging("Server side error has occured on host $mnethostid: " . |
c72fe801 | 771 | join("\n", $mnet_request->error)); |
3d7e4468 | 772 | break; |
c72fe801 | 773 | } |
c72fe801 | 774 | } |
775 | } | |
776 | ||
777 | /** | |
778 | * Receives an array of log entries from an SP and adds them to the mnet_log | |
779 | * table | |
780 | * | |
8bc663e8 | 781 | * @deprecated since Moodle 2.8 Please don't use this function for recording mnet logs. |
c72fe801 | 782 | * @param array $array An array of usernames |
783 | * @return string "All ok" or an error message | |
784 | */ | |
785 | function refresh_log($array) { | |
8bc663e8 AG |
786 | debugging('refresh_log() is deprecated, The transfer of logs through mnet are no longer recorded.', DEBUG_DEVELOPER); |
787 | return array('code' => 0, 'message' => 'All ok'); | |
c72fe801 | 788 | } |
789 | ||
790 | /** | |
791 | * Receives an array of usernames from a remote machine and prods their | |
792 | * sessions to keep them alive | |
793 | * | |
794 | * @param array $array An array of usernames | |
795 | * @return string "All ok" or an error message | |
796 | */ | |
797 | function keepalive_server($array) { | |
287efec6 PL |
798 | global $CFG, $DB; |
799 | $remoteclient = get_mnet_remote_client(); | |
c72fe801 | 800 | |
c72fe801 | 801 | // We don't want to output anything to the client machine |
802 | $start = ob_start(); | |
803 | ||
804 | // We'll get session records in batches of 30 | |
805 | $superArray = array_chunk($array, 30); | |
806 | ||
807 | $returnString = ''; | |
808 | ||
809 | foreach($superArray as $subArray) { | |
810 | $subArray = array_values($subArray); | |
811 | $instring = "('".implode("', '",$subArray)."')"; | |
7b0d12b2 | 812 | $query = "select id, session_id, username from {mnet_session} where username in $instring"; |
813 | $results = $DB->get_records_sql($query); | |
c72fe801 | 814 | |
815 | if ($results == false) { | |
816 | // We seem to have a username that breaks our query: | |
817 | // TODO: Handle this error appropriately | |
818 | $returnString .= "We failed to refresh the session for the following usernames: \n".implode("\n", $subArray)."\n\n"; | |
819 | } else { | |
c72fe801 | 820 | foreach($results as $emigrant) { |
d79d5ac2 | 821 | \core\session\manager::touch_session($emigrant->session_id); |
c72fe801 | 822 | } |
c72fe801 | 823 | } |
824 | } | |
825 | ||
826 | $end = ob_end_clean(); | |
827 | ||
287efec6 PL |
828 | if (empty($returnString)) return array('code' => 0, 'message' => 'All ok', 'last log id' => $remoteclient->last_log_id); |
829 | return array('code' => 1, 'message' => $returnString, 'last log id' => $remoteclient->last_log_id); | |
c72fe801 | 830 | } |
831 | ||
832 | /** | |
833 | * Cron function will be called automatically by cron.php every 5 minutes | |
834 | * | |
835 | * @return void | |
836 | */ | |
837 | function cron() { | |
7b0d12b2 | 838 | global $DB; |
4c1c5d26 | 839 | |
840 | // run the keepalive client | |
c72fe801 | 841 | $this->keepalive_client(); |
4c1c5d26 | 842 | |
4c1c5d26 | 843 | $random100 = rand(0,100); |
844 | if ($random100 < 10) { // Approximately 10% of the time. | |
845 | // nuke olden sessions | |
f71a7f8f | 846 | $longtime = time() - (1 * 3600 * 24); |
7b0d12b2 | 847 | $DB->delete_records_select('mnet_session', "expires < ?", array($longtime)); |
4c1c5d26 | 848 | } |
c72fe801 | 849 | } |
850 | ||
851 | /** | |
852 | * Cleanup any remote mnet_sessions, kill the local mnet_session data | |
853 | * | |
854 | * This is called by require_logout in moodlelib | |
855 | * | |
856 | * @return void | |
857 | */ | |
f5fd4347 | 858 | function prelogout_hook() { |
287efec6 PL |
859 | global $CFG, $USER; |
860 | ||
23a94798 | 861 | if (!is_enabled_auth('mnet')) { |
f5fd4347 | 862 | return; |
863 | } | |
864 | ||
c72fe801 | 865 | // If the user is local to this Moodle: |
287efec6 | 866 | if ($USER->mnethostid == $this->mnet->id) { |
c72fe801 | 867 | $this->kill_children($USER->username, sha1($_SERVER['HTTP_USER_AGENT'])); |
868 | ||
869 | // Else the user has hit 'logout' at a Service Provider Moodle: | |
870 | } else { | |
871 | $this->kill_parent($USER->username, sha1($_SERVER['HTTP_USER_AGENT'])); | |
872 | ||
873 | } | |
874 | } | |
875 | ||
876 | /** | |
877 | * The SP uses this function to kill the session on the parent IdP | |
878 | * | |
879 | * @param string $username Username for session to kill | |
880 | * @param string $useragent SHA1 hash of user agent to look for | |
881 | * @return string A plaintext report of what has happened | |
882 | */ | |
883 | function kill_parent($username, $useragent) { | |
7b0d12b2 | 884 | global $CFG, $USER, $DB; |
885 | ||
c72fe801 | 886 | require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; |
887 | $sql = " | |
888 | select | |
889 | * | |
890 | from | |
7b0d12b2 | 891 | {mnet_session} s |
c72fe801 | 892 | where |
7b0d12b2 | 893 | s.username = ? AND |
894 | s.useragent = ? AND | |
895 | s.mnethostid = ?"; | |
c72fe801 | 896 | |
7b0d12b2 | 897 | $mnetsessions = $DB->get_records_sql($sql, array($username, $useragent, $USER->mnethostid)); |
c72fe801 | 898 | |
7b0d12b2 | 899 | $ignore = $DB->delete_records('mnet_session', |
900 | array('username'=>$username, | |
901 | 'useragent'=>$useragent, | |
902 | 'mnethostid'=>$USER->mnethostid)); | |
c72fe801 | 903 | |
904 | if (false != $mnetsessions) { | |
905 | $mnet_peer = new mnet_peer(); | |
906 | $mnet_peer->set_id($USER->mnethostid); | |
907 | ||
908 | $mnet_request = new mnet_xmlrpc_client(); | |
909 | $mnet_request->set_method('auth/mnet/auth.php/kill_children'); | |
910 | ||
911 | // set $token and $useragent parameters | |
912 | $mnet_request->add_param($username); | |
913 | $mnet_request->add_param($useragent); | |
914 | if ($mnet_request->send($mnet_peer) === false) { | |
915 | debugging(join("\n", $mnet_request->error)); | |
139ebfdb | 916 | return false; |
c72fe801 | 917 | } |
918 | } | |
919 | ||
c72fe801 | 920 | return true; |
921 | } | |
922 | ||
923 | /** | |
924 | * The IdP uses this function to kill child sessions on other hosts | |
925 | * | |
926 | * @param string $username Username for session to kill | |
927 | * @param string $useragent SHA1 hash of user agent to look for | |
928 | * @return string A plaintext report of what has happened | |
929 | */ | |
930 | function kill_children($username, $useragent) { | |
287efec6 | 931 | global $CFG, $USER, $DB; |
48fb3941 PL |
932 | $remoteclient = null; |
933 | if (defined('MNET_SERVER')) { | |
934 | $remoteclient = get_mnet_remote_client(); | |
935 | } | |
c72fe801 | 936 | require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; |
937 | ||
7b0d12b2 | 938 | $userid = $DB->get_field('user', 'id', array('mnethostid'=>$CFG->mnet_localhost_id, 'username'=>$username)); |
c72fe801 | 939 | |
940 | $returnstring = ''; | |
1c85006c | 941 | |
942 | $mnetsessions = $DB->get_records('mnet_session', array('userid' => $userid, 'useragent' => $useragent)); | |
c72fe801 | 943 | |
c72fe801 | 944 | if (false == $mnetsessions) { |
f213ba93 | 945 | $returnstring .= "Could find no remote sessions\n"; |
c72fe801 | 946 | $mnetsessions = array(); |
947 | } | |
948 | ||
949 | foreach($mnetsessions as $mnetsession) { | |
4711957d | 950 | // If this script is being executed by a remote peer, that means the user has clicked |
951 | // logout on that peer, and the session on that peer can be deleted natively. | |
952 | // Skip over it. | |
287efec6 | 953 | if (isset($remoteclient->id) && ($mnetsession->mnethostid == $remoteclient->id)) { |
4711957d | 954 | continue; |
955 | } | |
c72fe801 | 956 | $returnstring .= "Deleting session\n"; |
957 | ||
c72fe801 | 958 | $mnet_peer = new mnet_peer(); |
959 | $mnet_peer->set_id($mnetsession->mnethostid); | |
960 | ||
961 | $mnet_request = new mnet_xmlrpc_client(); | |
962 | $mnet_request->set_method('auth/mnet/auth.php/kill_child'); | |
963 | ||
964 | // set $token and $useragent parameters | |
965 | $mnet_request->add_param($username); | |
966 | $mnet_request->add_param($useragent); | |
967 | if ($mnet_request->send($mnet_peer) === false) { | |
1a7601ca | 968 | debugging("Server side error has occured on host $mnetsession->mnethostid: " . |
c72fe801 | 969 | join("\n", $mnet_request->error)); |
970 | } | |
971 | } | |
972 | ||
7b0d12b2 | 973 | $ignore = $DB->delete_records('mnet_session', |
974 | array('useragent'=>$useragent, 'userid'=>$userid)); | |
c72fe801 | 975 | |
287efec6 | 976 | if (isset($remoteclient) && isset($remoteclient->id)) { |
d79d5ac2 | 977 | \core\session\manager::kill_user_sessions($userid); |
c72fe801 | 978 | } |
979 | return $returnstring; | |
980 | } | |
981 | ||
982 | /** | |
2e38d703 | 983 | * When the IdP requests that child sessions are terminated, |
c72fe801 | 984 | * this function will be called on each of the child hosts. The machine that |
985 | * calls the function (over xmlrpc) provides us with the mnethostid we need. | |
986 | * | |
987 | * @param string $username Username for session to kill | |
b584a358 | 988 | * @param string $useragent SHA1 hash of user agent to look for |
c72fe801 | 989 | * @return bool True on success |
990 | */ | |
991 | function kill_child($username, $useragent) { | |
287efec6 PL |
992 | global $CFG, $DB; |
993 | $remoteclient = get_mnet_remote_client(); | |
b584a358 DP |
994 | $session = $DB->get_record('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent)); |
995 | $DB->delete_records('mnet_session', array('username'=>$username, 'mnethostid'=>$remoteclient->id, 'useragent'=>$useragent)); | |
c72fe801 | 996 | if (false != $session) { |
d79d5ac2 | 997 | \core\session\manager::kill_session($session->session_id); |
c72fe801 | 998 | return true; |
999 | } | |
1000 | return false; | |
1001 | } | |
1002 | ||
1003 | /** | |
1004 | * To delete a host, we must delete all current sessions that users from | |
1005 | * that host are currently engaged in. | |
1006 | * | |
1007 | * @param string $sessionidarray An array of session hashes | |
1008 | * @return bool True on success | |
1009 | */ | |
1010 | function end_local_sessions(&$sessionArray) { | |
1011 | global $CFG; | |
1012 | if (is_array($sessionArray)) { | |
c72fe801 | 1013 | while($session = array_pop($sessionArray)) { |
d79d5ac2 | 1014 | \core\session\manager::kill_session($session->session_id); |
c72fe801 | 1015 | } |
c72fe801 | 1016 | return true; |
1017 | } | |
1018 | return false; | |
1019 | } | |
1020 | ||
1021 | /** | |
35d76df3 | 1022 | * Returns the user's profile image info |
c72fe801 | 1023 | * |
35d76df3 DM |
1024 | * If the user exists and has a profile picture, the returned array will contain keys: |
1025 | * f1 - the content of the default 100x100px image | |
1026 | * f1_mimetype - the mimetype of the f1 file | |
1027 | * f2 - the content of the 35x35px variant of the image | |
1028 | * f2_mimetype - the mimetype of the f2 file | |
1029 | * | |
1030 | * The mimetype information was added in Moodle 2.0. In Moodle 1.x, images are always jpegs. | |
1031 | * | |
1032 | * @see process_new_icon() | |
1033 | * @uses mnet_remote_client callable via MNet XML-RPC | |
052141ab | 1034 | * @param int $username The id of the user |
35d76df3 | 1035 | * @return false|array false if user not found, empty array if no picture exists, array with data otherwise |
c72fe801 | 1036 | */ |
1037 | function fetch_user_image($username) { | |
7b0d12b2 | 1038 | global $CFG, $DB; |
c72fe801 | 1039 | |
35d76df3 DM |
1040 | if ($user = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id))) { |
1041 | $fs = get_file_storage(); | |
bf0f06b1 | 1042 | $usercontext = context_user::instance($user->id, MUST_EXIST); |
c72fe801 | 1043 | $return = array(); |
35d76df3 DM |
1044 | if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) { |
1045 | $return['f1'] = base64_encode($f1->get_content()); | |
1046 | $return['f1_mimetype'] = $f1->get_mimetype(); | |
1047 | } else if ($f1 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) { | |
1048 | $return['f1'] = base64_encode($f1->get_content()); | |
1049 | $return['f1_mimetype'] = $f1->get_mimetype(); | |
c72fe801 | 1050 | } |
35d76df3 DM |
1051 | if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.png')) { |
1052 | $return['f2'] = base64_encode($f2->get_content()); | |
1053 | $return['f2_mimetype'] = $f2->get_mimetype(); | |
1054 | } else if ($f2 = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f2.jpg')) { | |
1055 | $return['f2'] = base64_encode($f2->get_content()); | |
1056 | $return['f2_mimetype'] = $f2->get_mimetype(); | |
c72fe801 | 1057 | } |
1058 | return $return; | |
1059 | } | |
1060 | return false; | |
1061 | } | |
1062 | ||
1063 | /** | |
1064 | * Returns the theme information and logo url as strings. | |
1065 | * | |
1066 | * @return string The theme info | |
1067 | */ | |
1068 | function fetch_theme_info() { | |
1069 | global $CFG; | |
1070 | ||
1071 | $themename = "$CFG->theme"; | |
1072 | $logourl = "$CFG->wwwroot/theme/$CFG->theme/images/logo.jpg"; | |
1073 | ||
1074 | $return['themename'] = $themename; | |
1075 | $return['logourl'] = $logourl; | |
1076 | return $return; | |
1077 | } | |
1078 | ||
1079 | /** | |
1080 | * Determines if an MNET host is providing the nominated service. | |
1081 | * | |
1082 | * @param int $mnethostid The id of the remote host | |
1083 | * @param string $servicename The name of the service | |
1084 | * @return bool Whether the service is available on the remote host | |
1085 | */ | |
1086 | function has_service($mnethostid, $servicename) { | |
7b0d12b2 | 1087 | global $CFG, $DB; |
c72fe801 | 1088 | |
1089 | $sql = " | |
1090 | SELECT | |
1091 | svc.id as serviceid, | |
1092 | svc.name, | |
1093 | svc.description, | |
1094 | svc.offer, | |
1095 | svc.apiversion, | |
1096 | h2s.id as h2s_id | |
1097 | FROM | |
dbca4e44 | 1098 | {mnet_host} h, |
7b0d12b2 | 1099 | {mnet_service} svc, |
1100 | {mnet_host2service} h2s | |
c72fe801 | 1101 | WHERE |
dbca4e44 | 1102 | h.deleted = '0' AND |
1103 | h.id = h2s.hostid AND | |
7b0d12b2 | 1104 | h2s.hostid = ? AND |
c72fe801 | 1105 | h2s.serviceid = svc.id AND |
7b0d12b2 | 1106 | svc.name = ? AND |
c72fe801 | 1107 | h2s.subscribe = '1'"; |
1108 | ||
7b0d12b2 | 1109 | return $DB->get_records_sql($sql, array($mnethostid, $servicename)); |
c72fe801 | 1110 | } |
1111 | ||
1112 | /** | |
1113 | * Checks the MNET access control table to see if the username/mnethost | |
1114 | * is permitted to login to this moodle. | |
1115 | * | |
1116 | * @param string $username The username | |
1117 | * @param int $mnethostid The id of the remote mnethost | |
1118 | * @return bool Whether the user can login from the remote host | |
1119 | */ | |
1120 | function can_login_remotely($username, $mnethostid) { | |
7b0d12b2 | 1121 | global $DB; |
1122 | ||
cdf22329 | 1123 | $accessctrl = 'allow'; |
7b0d12b2 | 1124 | $aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnethostid)); |
c72fe801 | 1125 | if (!empty($aclrecord)) { |
cdf22329 | 1126 | $accessctrl = $aclrecord->accessctrl; |
c72fe801 | 1127 | } |
cdf22329 | 1128 | return $accessctrl == 'allow'; |
c72fe801 | 1129 | } |
6bc1e5d5 | 1130 | |
f5fd4347 | 1131 | function logoutpage_hook() { |
7b0d12b2 | 1132 | global $USER, $CFG, $redirect, $DB; |
6bc1e5d5 | 1133 | |
1134 | if (!empty($USER->mnethostid) and $USER->mnethostid != $CFG->mnet_localhost_id) { | |
7b0d12b2 | 1135 | $host = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid)); |
6bc1e5d5 | 1136 | $redirect = $host->wwwroot.'/'; |
1137 | } | |
1138 | } | |
1139 | ||
9dbc81ef | 1140 | /** |
1141 | * Trims a log line from mnet peer to limit each part to a length which can be stored in our DB | |
1142 | * | |
1143 | * @param object $logline The log information to be trimmed | |
1144 | * @return object The passed logline object trimmed to not exceed storable limits | |
1145 | */ | |
1146 | function trim_logline ($logline) { | |
1147 | $limits = array('ip' => 15, 'coursename' => 40, 'module' => 20, 'action' => 40, | |
1148 | 'url' => 255); | |
1149 | foreach ($limits as $property => $limit) { | |
1150 | if (isset($logline->$property)) { | |
1151 | $logline->$property = substr($logline->$property, 0, $limit); | |
1152 | } | |
1153 | } | |
1154 | ||
1155 | return $logline; | |
1156 | } | |
1157 | ||
b257d7c4 PL |
1158 | /** |
1159 | * Returns a list of potential IdPs that this authentication plugin supports. | |
1160 | * This is used to provide links on the login page. | |
1161 | * | |
1162 | * @param string $wantsurl the relative url fragment the user wants to get to. You can use this to compose a returnurl, for example | |
1163 | * | |
1164 | * @return array like: | |
1165 | * array( | |
1166 | * array( | |
1167 | * 'url' => 'http://someurl', | |
1168 | * 'icon' => new pix_icon(...), | |
1169 | * 'name' => get_string('somename', 'auth_yourplugin'), | |
1170 | * ), | |
1171 | * ) | |
1172 | */ | |
1173 | function loginpage_idp_list($wantsurl) { | |
1174 | global $DB, $CFG; | |
4d0552e8 | 1175 | |
b257d7c4 PL |
1176 | // strip off wwwroot, since the remote site will prefix it's return url with this |
1177 | $wantsurl = preg_replace('/(' . preg_quote($CFG->wwwroot, '/') . '|' . preg_quote($CFG->httpswwwroot, '/') . ')/', '', $wantsurl); | |
4d0552e8 DM |
1178 | |
1179 | $sql = "SELECT DISTINCT h.id, h.wwwroot, h.name, a.sso_jump_url, a.name as application | |
1180 | FROM {mnet_host} h | |
1181 | JOIN {mnet_host2service} m ON h.id = m.hostid | |
1182 | JOIN {mnet_service} s ON s.id = m.serviceid | |
1183 | JOIN {mnet_application} a ON h.applicationid = a.id | |
1184 | WHERE s.name = ? AND h.deleted = ? AND m.publish = ?"; | |
1185 | $params = array('sso_sp', 0, 1); | |
1186 | ||
1187 | if (!empty($CFG->mnet_all_hosts_id)) { | |
1188 | $sql .= " AND h.id <> ?"; | |
1189 | $params[] = $CFG->mnet_all_hosts_id; | |
1190 | } | |
1191 | ||
1192 | if (!$hosts = $DB->get_records_sql($sql, $params)) { | |
b257d7c4 PL |
1193 | return array(); |
1194 | } | |
4d0552e8 | 1195 | |
b257d7c4 PL |
1196 | $idps = array(); |
1197 | foreach ($hosts as $host) { | |
1198 | $idps[] = array( | |
1199 | 'url' => new moodle_url($host->wwwroot . $host->sso_jump_url, array('hostwwwroot' => $CFG->wwwroot, 'wantsurl' => $wantsurl, 'remoteurl' => 1)), | |
1200 | 'icon' => new pix_icon('i/' . $host->application . '_host', $host->name), | |
1201 | 'name' => $host->name, | |
1202 | ); | |
1203 | } | |
1204 | return $idps; | |
1205 | } | |
c72fe801 | 1206 | } |