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