lib.php 38 KB
Newer Older
Donal McMullan's avatar
Donal McMullan committed
1
2
<?php
/**
Francois Marier's avatar
Francois Marier committed
3
 * Mahara: Electronic portfolio, weblog, resume builder and social networking
4
 * Copyright (C) 2006-2008 Catalyst IT Ltd (http://www.catalyst.net.nz)
Donal McMullan's avatar
Donal McMullan committed
5
 *
Francois Marier's avatar
Francois Marier committed
6
7
8
9
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
Donal McMullan's avatar
Donal McMullan committed
10
 *
Francois Marier's avatar
Francois Marier committed
11
12
13
14
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
Donal McMullan's avatar
Donal McMullan committed
15
 *
Francois Marier's avatar
Francois Marier committed
16
17
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Donal McMullan's avatar
Donal McMullan committed
18
19
20
 *
 * @package    mahara
 * @subpackage auth-internal
21
 * @author     Catalyst IT Ltd
Donal McMullan's avatar
Donal McMullan committed
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
23
 * @copyright  (C) 2006-2008 Catalyst IT Ltd http://catalyst.net.nz
Donal McMullan's avatar
Donal McMullan committed
24
25
26
27
 *
 */

defined('INTERNAL') || die();
28
require_once(get_config('docroot') . 'auth/lib.php');
29
require_once(get_config('libroot') . 'peer.php');
30
require_once(get_config('libroot') . 'applicationset.php');
31
require_once(get_config('docroot') . 'api/xmlrpc/lib.php');
Donal McMullan's avatar
Donal McMullan committed
32
33
34
35
36
37
38

/**
 * The XMLRPC authentication method, which authenticates users against the
 * ID Provider's XMLRPC service. This is special - it doesn't extend Auth, it's
 * not static, and it doesn't implement the expected methods. It doesn't replace
 * the user's existing Auth type, whatever that might be; it supplements it.
 */
39
class AuthXmlrpc extends Auth {
Donal McMullan's avatar
Donal McMullan committed
40

41
    /**findByWwwroot
42
43
44
45
46
47
     * Get the party started with an optional id
     * TODO: appraise
     * @param int $id   The auth instance id
     */
    public function __construct($id = null) {

48
        $this->has_instance_config = true;
49
50
        $this->type                            = 'xmlrpc';

51
52
        $this->config['wwwroot']               = '';
        $this->config['wwwroot_orig']          = '';
53
54
        $this->config['shortname']             = '';
        $this->config['name']                  = '';
55
        $this->config['portno']                = 80;
56
57
        $this->config['xmlrpcserverurl']       = '';
        $this->config['changepasswordurl']     = '';
58
        $this->config['updateuserinfoonlogin'] = 0;
59
60
        $this->config['weautocreateusers']     = 0;
        $this->config['theyautocreateusers']   = 0;
61
        $this->config['wessoout']              = 0;
62
        $this->config['theyssoin']             = 0;
63
        $this->config['weimportcontent']       = 0;
64
        $this->config['parent']                = null;
65
        $this->config['authloginmsg']          = '';
66
        if (!empty($id)) {
67
68
69
70
71
72
73
74
75
76
77
78
            return $this->init($id);
        }
        return true;
    }

    /**
     * Get config variables
     */
    public function init($id = null) {
        $this->ready = parent::init($id);
        return $this->ready;
    }
Donal McMullan's avatar
Donal McMullan committed
79

80
81
82
83
    public function __get($name) {
        if (array_key_exists($name, $this->config)) {
            return $this->config[$name];
        }
84
85
86
        if (isset($this->$name)) {
            return $this->$name;
        }
87
88
    }

89
90
91
92
93
94
95
96
97
98
99
100
    /**
     * The keepalive_client function is tricky to implement in Mahara. Moodle 
     * accomplishes this simply, because that application already updates the user 
     * table once for every page view.
     * I think that we *really* don't want to do that with Mahara. There are heaps of
     * ways that we could implement this that are not very portable, but for now, it's
     * best if we leave this on the todo pile. If it becomes crucially important for a 
     * stakeholder, we can provide some implementation of it.
     */
    public static function keepalive_client() {}
    public static function keepalive_server() {}

Donal McMullan's avatar
Donal McMullan committed
101
102
103
    /**
     * Grab a delegate object for auth stuff
     */
104
    public function request_user_authorise($token, $remotewwwroot) {
105
        global $USER, $SESSION;
106
        $this->must_be_ready();
Donal McMullan's avatar
Donal McMullan committed
107
108
        $peer = get_peer($remotewwwroot);

109
        if ($peer->deleted != 0 || $this->config['theyssoin'] != 1) {
110
            throw new XmlrpcClientException('We don\'t accept SSO connections from ' . $peer->name);
Donal McMullan's avatar
Donal McMullan committed
111
112
113
114
115
116
117
118
119
120
        }

        $client = new Client();
        $client->set_method('auth/mnet/auth.php/user_authorise')
               ->add_param($token)
               ->add_param(sha1($_SERVER['HTTP_USER_AGENT']))
               ->send($remotewwwroot);

        $remoteuser = (object)$client->response;

121
        if (empty($remoteuser) or !property_exists($remoteuser, 'username')) {
122
123
            // Caught by land.php
            throw new AccessDeniedException();
Donal McMullan's avatar
Donal McMullan committed
124
125
126
127
128
        }

        $virgin = false;

        $oldlastlogin = null;
129
130
        $create = false;
        $update = false;
Donal McMullan's avatar
Donal McMullan committed
131

132
        // Retrieve a $user object. If that fails, create a blank one.
133
        try {
134
            $user = new User;
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
            if (get_config('usersuniquebyusername')) {
                // When turned on, this setting means that it doesn't matter 
                // which other application the user SSOs from, they will be 
                // given the same account in Mahara.
                //
                // This setting is one that has security implications unless 
                // only turned on by people who know what they're doing. In 
                // particular, every system linked to Mahara should be making 
                // sure that same username == same person.  This happens for 
                // example if two Moodles are using the same LDAP server for 
                // authentication.
                //
                // If this setting is on, it must NOT be possible to self 
                // register on the site for ANY institution - otherwise users 
                // could simply pick usernames of people's accounts they wished 
                // to steal.
                if ($institutions = get_column('institution', 'name', 'registerallowed', '1')) {
                    log_warn("usersuniquebyusername is turned on but registration is allowed for an institution. "
                        . "No institution can have registration allowed for it, for security reasons.\n"
                        . "The following institutions have registration enabled:\n  " . join("\n  ", $institutions));
                    throw new AccessDeniedException();
                }

                if (!get_config('usersallowedmultipleinstitutions')) {
                    log_warn("usersuniquebyusername is turned on but usersallowedmultipleinstitutions is off. "
                        . "This makes no sense, as users will then change institution every time they log in from "
                        . "somewhere else. Please turn this setting on in Site Options");
                    throw new AccessDeniedException();
                }

                $user->find_by_username($remoteuser->username);
            }
            else {
                $user->find_by_instanceid_username($this->instanceid, $remoteuser->username, true);
            }

171
            if ($user->get('suspendedcusr')) {
172
                die_info(get_string('accountsuspended', 'mahara', strftime(get_string('strftimedaydate'), $user->get('suspendedctime')), $user->get('suspendedreason')));
173
            }
174

175
176
177
            if ('1' == $this->config['updateuserinfoonlogin']) {
                $update = true;
            }
178
        } catch (AuthUnknownUserException $e) {
179
            if (!empty($this->config['weautocreateusers'])) {
180
181
182
183
                $institution = new Institution($this->institution);
                if ($institution->isFull()) {
                    throw new XmlrpcClientException('SSO attempt from ' . $institution->displayname . ' failed - institution is full');
                }
184
                $user = new User;
185
                $create = true;
186
187
188
189
            }
            else {
                log_debug("User authorisation request from $remotewwwroot failed - "
                    . "remote user '$remoteuser->username' is unknown to us and auto creation of users is turned off");
190
191
192
                return false;
            }
        }
193

194
195
196
        /*******************************************/

        if ($create) {
Donal McMullan's avatar
Donal McMullan committed
197

198
199
200
201
202
203
            $user->passwordchange     = 1;
            $user->active             = 1;
            $user->deleted            = 0;

            //TODO: import institution's expiry?:
            //$institution = new Institution($peer->institution);
204
            $user->expiry             = null;
205
206
207
208
209
210
211
212
213
            $user->expirymailsent     = 0;
            $user->lastlogin          = time();
    
            $user->firstname          = $remoteuser->firstname;
            $user->lastname           = $remoteuser->lastname;
            $user->email              = $remoteuser->email;

            //TODO: import institution's per-user-quota?:
            //$user->quota              = $userrecord->quota;
Donal McMullan's avatar
Donal McMullan committed
214
            $user->authinstance       = empty($this->config['parent']) ? $this->instanceid : $this->parent;
215
216
217

            db_begin();
            $user->username           = get_new_username($remoteuser->username);
218
219

            $user->id = create_user($user, array(), $peer->institution, $this, $remoteuser->username);
220

221
222
            $this->import_user_settings($user, $remoteuser);

223
224
225
226
227
228
229
230
            /*
             * We need to convert the object to a stdclass with its own
             * custom method because it uses overloaders in its implementation
             * and its properties wouldn't be visible to a simple cast operation
             * like (array)$user
             */
            $userobj = $user->to_stdclass();
            $userarray = (array)$userobj;
231
            db_commit();
232

233
234
235
236
237
            // Now we have fired the create event, we need to re-get the data 
            // for this user
            $user = new User;
            $user->find_by_id($userobj->id);

238
        } elseif ($update) {
239
240
241
242
243
244
            $simplefieldstoimport = array('firstname', 'lastname', 'email');
            foreach ($simplefieldstoimport as $field) {
                if ($user->$field != $remoteuser->$field) {
                    $user->$field = $remoteuser->$field;
                    set_profile_field($user->id, $field, $user->$field);
                }
245
246
            }

247
            $this->import_user_settings($user, $remoteuser);
248

249
            $user->lastlastlogin      = $user->lastlogin;
250
            $user->lastlogin          = time();
251

252
253
254
255
            //TODO: import institution's per-user-quota?:
            //$user->quota              = $userrecord->quota;
            $user->commit();
        }
256

257
258
259
260
261
        if (get_config('usersuniquebyusername')) {
            // Add them to the institution they have SSOed in by
            $user->join_institution($peer->institution);
        }

262

263
264
265
266
        // See if we need to create/update a profile Icon image
        if ($create || $update) {

            $client->set_method('auth/mnet/auth.php/fetch_user_image')
267
                   ->add_param($remoteuser->username)
268
269
270
271
272
273
274
275
276
277
278
279
280
281
                   ->send($remotewwwroot);

            $imageobject = (object)$client->response;

            $u = preg_replace('/[^A-Za-z0-9 ]/', '', $user->username);
            $filename = '/tmp/'.intval($this->instanceid).'_'.$u;

            if (array_key_exists('f1', $client->response)) {
                $imagecontents = base64_decode($client->response['f1']);
                file_put_contents($filename, $imagecontents);
                $imageexists = false;
                $icons       = false;

                if ($update) {
Donal McMullan's avatar
Donal McMullan committed
282
                    $newchecksum = sha1_file($filename);
283
                    $icons = get_records_select_array('artefact', 'artefacttype = \'profileicon\' AND owner = ? ', array($user->id), '', 'id');
284
285
                    if (false != $icons) {
                        foreach ($icons as $icon) {
286
                            $iconfile = get_config('dataroot') . 'artefact/file/profileicons/originals/' . ($icon->id % 256) . '/'.$icon->id;
Donal McMullan's avatar
Donal McMullan committed
287
                            $checksum = sha1_file($iconfile);
288
289
290
291
292
293
294
                            if ($newchecksum == $checksum) {
                                $imageexists = true;
                                unlink($filename);
                                break;
                            }
                        }
                    }
295
296
                }

297
298
299
                if (false == $imageexists) {
                    $filesize = filesize($filename);
                    if (!$user->quota_allowed($filesize)) {
300
                        $error = get_string('profileiconuploadexceedsquota', 'artefact.file', get_config('wwwroot'));
301
                    }
302
303

                    require_once('file.php');
304
                    $imagesize = getimagesize($filename);
305
                    if (!$imagesize || !is_image_type($imagesize[2])) {
306
                        $error = get_string('filenotimage');
307
308
                    }

309
310
                    $mime   = $imagesize['mime'];
                    $width  = $imagesize[0];
311
                    $height = $imagesize[1];
312
313
314
                    $imagemaxwidth  = get_config('imagemaxwidth');
                    $imagemaxheight = get_config('imagemaxheight');
                    if ($width > $imagemaxwidth || $height > $imagemaxheight) {
315
                        $error = get_string('profileiconimagetoobig', 'artefact.file', $width, $height, $imagemaxwidth, $imagemaxheight);
316
                    }
317

318
319
320
321
                    try {
                        $user->quota_add($filesize);
                    }
                    catch (QuotaException $qe) {
322
                        $error =  get_string('profileiconuploadexceedsquota', 'artefact.file', get_config('wwwroot'));
323
324
                    }

325
                    require_once(get_config('docroot') .'/artefact/lib.php');
326
                    require_once(get_config('docroot') .'/artefact/file/lib.php');
327
328
329
330
331
332

                    // Entry in artefact table
                    $artefact = new ArtefactTypeProfileIcon();
                    $artefact->set('owner', $user->id);
                    $artefact->set('title', 'Profile Icon');
                    $artefact->set('note', 'Profile Icon');
333
334
335
336
                    $artefact->set('size', $filesize);
                    $artefact->set('filetype', $mime);
                    $artefact->set('width', $width);
                    $artefact->set('height', $height);
337
338
339
340
341
                    $artefact->commit();

                    $id = $artefact->get('id');

                    // Move the file into the correct place.
342
                    $directory = get_config('dataroot') . 'artefact/file/profileicons/originals/' . ($id % 256) . '/';
343
344
345
346
347
348
349
350
                    check_dir_exists($directory);
                    rename($filename, $directory . $id);
                    if ($create || empty($icons)) {
                        $user->profileicon = $id;
                    }
                }

                $user->commit();
Donal McMullan's avatar
Donal McMullan committed
351
            }
352
        }
Donal McMullan's avatar
Donal McMullan committed
353

354
355
        /*******************************************/

356
        // We know who our user is now. Bring her back to life.
357
        $USER->reanimate($user->id, $this->instanceid);
358
359
360
361
362

        // Set session variables to let the application know this session was 
        // initiated by MNET. Don't forget that users could initiate their 
        // sessions without MNET sometimes, which is why this data is stored in 
        // the session object.
363
364
        $SESSION->set('mnetuser', $user->id);
        $SESSION->set('authinstance', $this->instanceid);
365

366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
        return true;
    }

    /**
     * Given a username, returns whether the user exists in the usr table
     *
     * @param string $username The username to attempt to identify
     * @return bool            Whether the username exists
     */
    public function user_exists($username) {
        $this->must_be_ready();
        $userrecord = false;

        // The user is likely to be associated with the parent instance
        if (is_numeric($this->config['parent']) && $this->config['parent'] > 0) {
            $_instanceid = $this->config['parent'];
            $userrecord = get_record('usr', 'LOWER(username)', strtolower($username), 'authinstance', $_instanceid);
Donal McMullan's avatar
Donal McMullan committed
383
        }
384
385
386
387
388
389
390
391
392
393

        if (empty($userrecord)) {
            $_instanceid = $this->instanceid;
            $userrecord = get_record('usr', 'LOWER(username)', strtolower($username), 'authinstance', $_instanceid);
        }

        if ($userrecord != false) {
            return $userrecord;
        }
        throw new AuthUnknownUserException("\"$username\" is not known to Auth");
Donal McMullan's avatar
Donal McMullan committed
394
395
396
    }

    /**
397
398
399
400
     * In practice, I don't think this method needs to return an accurate 
     * answer for this, because XMLRPC authentication doesn't use the standard 
     * authentication mechanisms, instead relying on land.php to handle 
     * everything.
Donal McMullan's avatar
Donal McMullan committed
401
     */
402
403
    public function can_auto_create_users() {
        return (bool)$this->config['weautocreateusers'];
Donal McMullan's avatar
Donal McMullan committed
404
405
    }

406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
    /**
     * Given a user and their remote user record, attempt to populate some of 
     * the user's profile fields and account settings from the remote data.
     *
     * This does not change the first name, last name or e-mail fields, as these are 
     * dealt with differently depending on whether we are creating the user 
     * record or updating it.
     *
     * This method attempts to set:
     *
     * * City
     * * Country
     * * Language
     * * Introduction
     * * WYSIWYG editor setting
     *
     * @param User $user
     * @param stdClass $remoteuser
     */
    private function import_user_settings($user, $remoteuser) {
        // City
        if (!empty($remoteuser->city)) {
            if (get_profile_field($user->id, 'city') != $remoteuser->city) {
                set_profile_field($user->id, 'city', $remoteuser->city);
            }
        }

        // Country
        if (!empty($remoteuser->country)) {
            $validcountries = array_keys(getoptions_country());
            $newcountry = strtolower($remoteuser->country);
            if (in_array($newcountry, $validcountries)) {
                set_profile_field($user->id, 'country', $newcountry);
            }
        }

        // Language
        if (!empty($remoteuser->lang)) {
            $validlanguages = array_keys(get_languages());
            $newlanguage = str_replace('_', '.', strtolower($remoteuser->lang));
            if (in_array($newlanguage, $validlanguages)) {
                set_account_preference($user->id, 'lang', $newlanguage);
                $user->set_account_preference('lang', $newlanguage);
            }
        }

        // Description
        if (isset($remoteuser->description)) {
            if (get_profile_field($user->id, 'introduction') != $remoteuser->description) {
                set_profile_field($user->id, 'introduction', $remoteuser->description);
            }
        }

        // HTML Editor setting
        if (isset($remoteuser->htmleditor)) {
            $htmleditor = ($remoteuser->htmleditor) ? 1 : 0;
            if ($htmleditor != get_account_preference($user->id, 'wysiwyg')) {
                set_account_preference($user->id, 'wysiwyg', $htmleditor);
                $user->set_account_preference('wysiwyg', $htmleditor);
            }
        }
    }

469
470
471
    public function kill_parent($username) {
        require_once(get_config('docroot') . 'api/xmlrpc/client.php');

472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
        // For some people, the call to kill_children fails (when the remote 
        // site is a Moodle). We still haven't worked out why that is, but it's 
        // not a problem on the Mahara site
        try {
            $client = new Client();
            $client->set_method('auth/mnet/auth.php/kill_children')
                   ->add_param($username)
                   ->add_param(sha1($_SERVER['HTTP_USER_AGENT']))
                   ->send($this->wwwroot);
        }
        catch (XmlrpcClientException $e) {
            log_debug("XMLRPC error occured while calling MNET method kill_children on $this->wwwroot");
            log_debug("This means that single-signout probably didn't work properly, but the problem "
                . "is at the remote application");
            log_debug("If the remote application is Moodle, and you're happy with a Mahara developer "
                . "getting access to your system so they can try and debug the problem, get in touch with dev@mahara.org");
            log_debug("Exception message follows:");
            log_debug($e->getMessage());
        }
491
492
    }

493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
    /**
     * Overrides the default logout mechanism to do proper single singout
     */
    public function logout() {
        global $USER, $SESSION;

        if (get_config('usersuniquebyusername')) {
            // The auth_remote_user will have a row for the institution in 
            // which the user SSOed into first. However, they could have 
            // been coming from somewhere else this time, which is why we 
            // can't use auth_remote_user for the lookup. Their username 
            // won't change for their Mahara account anyway, so just grab 
            // it out of the usr table.
            $remoteusername = get_field('usr', 'username', 'id', $USER->get('id'));
        }
        else {
            // Check the auth_remote_user table for what the remote 
            // application thinks the username is
            $remoteusername = get_field('auth_remote_user', 'remoteusername', 'localusr', $USER->get('id'), 'authinstance', $this->instanceid);
            if (!$remoteusername && $this->parent) {
                $remoteusername = get_field('auth_remote_user', 'remoteusername', 'localusr', $USER->get('id'), 'authinstance', $this->parent);
            }
        }

        $USER->logout();

        if (isset($_GET['logout'])) {
            // Explicit logout request
            $this->kill_parent($remoteusername);
            redirect($this->wwwroot);
        }
        elseif (!$this->parent) {
            $this->kill_parent($remoteusername);
            // Redirect back to their IDP if they don't have a parent auth method set 
            // (aka: they can't log in at Mahara's log in form)
            $peer = get_peer($this->wwwroot);
            // TODO: This should be stored in the application config table
            $jumpurl = str_replace('land', 'jump', $peer->application->ssolandurl);
            redirect($this->wwwroot . $jumpurl . '?hostwwwroot=' . dropslash(get_config('wwwroot')) . '&wantsurl=' . urlencode($_SERVER['REQUEST_URI']));
        }

        // Anything else is a session timeout

        $SESSION->set('mnetuser', null);
    }

Donal McMullan's avatar
Donal McMullan committed
539
540
541
542
543
}

/**
 * Plugin configuration class
 */
544
class PluginAuthXmlrpc extends PluginAuth {
Donal McMullan's avatar
Donal McMullan committed
545

546
547
548
549
550
551
552
    private static $default_config = array(
        'instancename'          => '',
        'wwwroot'               => '',
        'wwwroot_orig'          => '',
        'name'                  => '',
        'appname'               => '',
        'portno'                => 80,
553
        'updateuserinfoonlogin' => 0,
554
555
        'weautocreateusers'     => 0,
        'theyautocreateusers'   => 0,
556
557
        'wessoout'              => 0,
        'theyssoin'             => 0,
558
        'weimportcontent'       => 0,
559
560
        'parent'                => null,
        'authloginmsg'          => ''
561
    );
562

Donal McMullan's avatar
Donal McMullan committed
563
    public static function has_config() {
564
565
566
567
568
569
570
571
        return false;
    }

    public static function get_config_options() {
        return array();
    }

    public static function has_instance_config() {
572
        return true;
Donal McMullan's avatar
Donal McMullan committed
573
574
    }

575
576
577
578
    public static function is_usable() {
        return extension_loaded('xmlrpc') && extension_loaded('openssl') && extension_loaded('curl');
    }

579
    public static function get_instance_config_options($institution, $instance = 0) {
580

581
582
        $peer = new Peer();

Donal McMullan's avatar
Donal McMullan committed
583
        // TODO : switch to getrecord
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
        // Get a list of applications and make a dropdown from it
        $applicationset = new ApplicationSet();
        $apparray = array();
        foreach ($applicationset as $app) {
            $apparray[$app->name] = $app->displayname;
        }

        /**
         * A parent authority for XML-RPC is the data-source that a remote XML-RPC service
         * communicates with to authenticate a user, for example, the XML-RPC server that 
         * we connect to might be authorising users against an LDAP store. If this is the 
         * case, and we know of the LDAP store, and our users are able to log on to our 
         * system and be authenticated directly against the LDAP store, then we honor that 
         * association.
         * 
         * In this way, the unique relationship is between the username and the authority,
         * not the username and the institution. This allows an institution to have a user
         * 'donal' on server 'LDAP-1' and a different user 'donal' on server 'LDAP-2'.
         * 
         * Get a list of auth instances for this institution, and eliminate those that 
         * would not be valid parents (as they themselves require a parent). These are 
         * eliminated only to provide a saner interface to the admin user. In theory, it's
         * ok to chain authorities.
         */ 
        $instances = auth_get_auth_instances_for_institution($institution);
        $options = array('None');
        if (is_array($instances)) {
            foreach($instances as $someinstance) {
                if ($someinstance->requires_parent == 1) {
                    continue;
                }
                $options[$someinstance->id] = $someinstance->instancename;
            }
        }

        // Get the current data (if any exists) for this auth instance
620
        if ($instance > 0) {
621
            $default = get_record('auth_instance', 'id', $instance);
622
            if ($default == false) {
623
                throw new SystemException(get_string('nodataforinstance', 'auth').$instance);
624
625
626
            }
            $current_config = get_records_menu('auth_instance_config', 'instance', $instance, '', 'field, value');

627
            if ($current_config == false) {
628
                throw new SystemException('No config data for instance: '.$instance);
629
630
631
            }

            foreach (self::$default_config as $key => $value) {
632
                if (array_key_exists($key, $current_config)) {
633
                    self::$default_config[$key] = $current_config[$key];
634
635
636
637
638
639
640
641

                    // We can use the wwwroot to create a Peer object
                    if ('wwwroot' == $key) {
                        $peer->findByWwwroot($current_config[$key]);
                        self::$default_config['wwwroot_orig'] = $current_config[$key];
                    }
                } elseif (property_exists($default, $key)) {
                    self::$default_config[$key] = $default->{$key};
642
643
644
                }
            }
        } else {
645
646
647
648
649
650
651
652
            $max_priority = get_field('auth_instance', 'MAX(priority)', 'institution', $institution);
            self::$default_config['priority'] = ++$max_priority;
        }

        if (empty($peer->application->name)) {
            self::$default_config['appname'] = key(current($applicationset));
        } else {
            self::$default_config['appname'] = $peer->application->name;
653
654
655
656
657
658
659
660
        }

        $elements['instancename'] = array(
            'type' => 'text',
            'title' => get_string('authname','auth'),
            'rules' => array(
                'required' => true
            ),
661
            'defaultvalue' => self::$default_config['instancename'],
662
663
664
665
666
667
668
669
670
671
672
673
674
            'help'   => true
        );

        $elements['instance'] = array(
            'type' => 'hidden',
            'value' => $instance
        );

        $elements['institution'] = array(
            'type' => 'hidden',
            'value' => $institution
        );

675
676
677
678
679
        $elements['deleted'] = array(
            'type' => 'hidden',
            'value' => $peer->deleted
        );

680
681
682
683
684
        $elements['authname'] = array(
            'type' => 'hidden',
            'value' => 'xmlrpc'
        );

685
        $elements['wwwroot'] = array(
686
            'type' => 'text',
687
            'title' => get_string('wwwroot', 'auth'),
688
689
690
            'rules' => array(
                'required' => true
            ),
691
            'defaultvalue' => self::$default_config['wwwroot'],
692
693
694
            'help'   => true
        );

695
696
697
698
699
700
701
702
703
704
        $elements['wwwroot_orig'] = array(
            'type' => 'hidden',
            'value' => self::$default_config['wwwroot_orig']
        );

        $elements['oldwwwroot'] = array(
            'type' => 'hidden',
            'value' => 'xmlrpc'
        );

705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
        if ($instance) {
            $elements['publickey'] = array(
                'type' => 'textarea',
                'title' => get_string('publickey', 'admin'),
                'defaultvalue' => get_field('host', 'publickey', 'wwwroot', self::$default_config['wwwroot']),
                'rules' => array(
                    'required' => true,
                ),
                'rows' => 15,
                'cols' => 70,
            );

            $elements['publickeyexpires']= array(
                'type' => 'html',
                'title' => get_string('publickeyexpires', 'admin'),
                'value' => format_date(get_field('host', 'publickeyexpires', 'wwwroot', self::$default_config['wwwroot'])),
            );
        }

724
725
726
727
728
729
        $elements['name'] = array(
            'type' => 'text',
            'title' => get_string('name', 'auth'),
            'rules' => array(
                'required' => true
            ),
730
            'defaultvalue' => $peer->name,
731
732
733
            'help'   => true
        );

734
735
736
737
738
739
740
741
742
        /**
         * empty($peer->appname) would ALWAYS return true, because the property doesn't really
         * exist. When we try to get $peer->appname, we're actually calling the peer class's
         * __get overloader. Unfortunately, the 'empty' function seems to just check for the
         * existence of the property - it doesn't call the overloader. Bug or feature?
         */
	     
        $tmpappname = $peer->appname;

743
744
745
746
747
748
        $elements['appname'] = array(
            'type'                => 'select',
            'title'               => get_string('application','auth'),
            'collapseifoneoption' => true,
            'multiple'            => false,
            'options'             => $apparray,
749
            'defaultvalue'        => empty($tmpappname)? 'moodle' : $tmpappname,
750
751
752
753
            'help'                => true
        );

        $elements['portno'] = array(
754
            'type' => 'text',
755
            'title' => get_string('port', 'auth'),
756
            'rules' => array(
757
758
                'required' => true,
                'integer'  => true
759
            ),
760
            'defaultvalue' => $peer->portno,
761
            'size'   => 4,
762
763
764
            'help'   => true
        );

765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
        $elements['parent'] = array(
            'type'                => 'select',
            'title'               => get_string('parent','auth'),
            'collapseifoneoption' => false,
            'options'             => $options,
            'defaultvalue'        => self::$default_config['parent'],
            'help'   => true
        );

        $elements['authloginmsg'] = array(
            'type'         => 'textarea',
            'rows'         => 3,
            'cols'         => 70,
            'title'        => '',
            'description'  => get_string('authloginmsg', 'auth'),
            'defaultvalue' => self::$default_config['authloginmsg'],
            'help'         => true,
        );

784
785
786
787
788
789
790
791
792
793
794
        $elements['wessoout'] = array(
            'type'         => 'checkbox',
            'title'        => get_string('wessoout', 'auth'),
            'defaultvalue' => self::$default_config['wessoout'],
            'help'   => true
        );

        $elements['theyssoin'] = array(
            'type'         => 'checkbox',
            'title'        => get_string('theyssoin', 'auth'),
            'defaultvalue' => self::$default_config['theyssoin'],
795
796
797
798
799
800
801
802
803
804
            'help'   => true
        );

        $elements['updateuserinfoonlogin'] = array(
            'type'         => 'checkbox',
            'title'        => get_string('updateuserinfoonlogin', 'auth'),
            'defaultvalue' => self::$default_config['updateuserinfoonlogin'],
            'help'   => true
        );

805
        $elements['weautocreateusers'] = array(
806
            'type'         => 'checkbox',
807
808
            'title'        => get_string('weautocreateusers', 'auth'),
            'defaultvalue' => self::$default_config['weautocreateusers'],
809
810
811
            'help'   => true
        );

812
813
814
815
816
817
        $elements['theyautocreateusers'] = array(
            'type'         => 'checkbox',
            'title'        => get_string('theyautocreateusers', 'auth'),
            'defaultvalue' => self::$default_config['theyautocreateusers'],
            'help'   => true
        );
818
819
820
821
822
823
824
825

        $elements['weimportcontent'] = array(
            'type'         => 'checkbox',
            'title'        => get_string('weimportcontent', 'auth'),
            'defaultvalue' => self::$default_config['weimportcontent'],
            'help'         => true,
        );

826
827
828
829
        return array(
            'elements' => $elements,
            'renderer' => 'table'
        );
Donal McMullan's avatar
Donal McMullan committed
830
    }
831

832
    public static function validate_config_options($values, $form) {
833
834
835
        if (false === strpos($values['wwwroot'], '://')) {
            $values['wwwroot'] = 'http://' . $values['wwwroot'];
        }
836
837
838
839

        $authinstance = new stdClass();
        $peer = new Peer();

840
841
842
843
844
845
846
847
        if (false == $peer->findByWwwroot($values['wwwroot'])) {
            try {
                $peer->bootstrap($values['wwwroot'], null, $values['appname'], $values['institution']);
            } catch (RemoteServerException $e) {
                $form->set_error('wwwroot',get_string('cantretrievekey', 'auth'));
            }
        }

848
849
850
851
852
853
854
855
856
        if (isset($values['publickey'])) {
            try {
                new PublicKey($values['publickey'], $peer->wwwroot);
            }
            catch (CryptException $e) {
                $form->set_error('publickey', $e->getMessage());
            }
        }

Donal McMullan's avatar
WIP    
Donal McMullan committed
857
        //TODO: test values and set appropriate errors on form
858
859
860
    }

    public static function save_config_options($values, $form) {
861
862
863
        if (false === strpos($values['wwwroot'], '://')) {
            $values['wwwroot'] = 'http://' . $values['wwwroot'];
        }
864

865
        db_begin();
866
        $authinstance = new stdClass();
867
        $peer = new Peer();
868
869
870
871
872

        if ($values['instance'] > 0) {
            $values['create'] = false;
            $current = get_records_assoc('auth_instance_config', 'instance', $values['instance'], '', 'field, value');
            $authinstance->id = $values['instance'];
873

874
875
876
877
878
879
880
881
882
883
884
885
886
887
        } else {
            $values['create'] = true;

            // Get the auth instance with the highest priority number (which is
            // the instance with the lowest priority).
            // TODO: rethink 'priority' as a fieldname... it's backwards!!
            $lastinstance = get_records_array('auth_instance', 'institution', $values['institution'], 'priority DESC', '*', '0', '1');

            if ($lastinstance == false) {
                $authinstance->priority = 0;
            } else {
                $authinstance->priority = $lastinstance[0]->priority + 1;
            }
        }
888
889
890
 
        if (false == $peer->findByWwwroot($values['wwwroot'])) {
            try {
Donal McMullan's avatar
WIP    
Donal McMullan committed
891
                $peer->bootstrap($values['wwwroot'], null, $values['appname'], $values['institution']);
892
893
894
895
896
897
            } catch (RemoteServerException $e) {
                $form->set_error('wwwroot',get_string('cantretrievekey', 'auth'));
                throw new RemoteServerException($e->getMessage(), $e->getCode());
            }
        }

898
        $peer->wwwroot              = preg_replace("|\/+$|", "", $values['wwwroot']);
899
900
901
902
903
        $peer->name                 = $values['name'];
        $peer->deleted              = $values['deleted'];
        $peer->portno               = $values['portno'];
        $peer->appname              = $values['appname'];
        $peer->institution          = $values['institution'];
904
905
906
907
        if (isset($values['publickey'])) {
            $peer->publickey            = new PublicKey($values['publickey'], $peer->wwwroot);
            $peer->publickeyexpires     = $peer->publickey->expires;
        }
908
909
910
911
912
913
914

        /**
         * The following properties are not user-updatable
        $peer->lastconnecttime      = $values['lastconnecttime'];
         */

        $peer->commit();
Donal McMullan's avatar
WIP    
Donal McMullan committed
915
        
916
917
918
919
920
921
922
923
924
925
926
927
928
929
        $authinstance->instancename = $values['instancename'];
        $authinstance->institution  = $values['institution'];
        $authinstance->authname     = $values['authname'];

        if ($values['create']) {
            $values['instance'] = insert_record('auth_instance', $authinstance, 'id', true);
        } else {
            update_record('auth_instance', $authinstance, array('id' => $values['instance']));
        }

        if (empty($current)) {
            $current = array();
        }

930
        self::$default_config = array(  'wwwroot'               => $values['wwwroot'],
931
                                        'updateuserinfoonlogin' => $values['updateuserinfoonlogin'],
932
933
                                        'weautocreateusers'     => $values['weautocreateusers'],
                                        'theyautocreateusers'   => $values['theyautocreateusers'],
934
                                        'parent'                => $values['parent'],
935
                                        'authloginmsg'          => $values['authloginmsg'],
936
                                        'wessoout'              => $values['wessoout'],
937
938
                                        'theyssoin'             => $values['theyssoin'],
                                        'weimportcontent'       => $values['weimportcontent'],
939
                                        );
940
941
942
943
944
945
946

        foreach(self::$default_config as $field => $value) {
            $record = new stdClass();
            $record->instance = $values['instance'];
            $record->field    = $field;
            $record->value    = $value;

947
948
949
950
            if ($field == 'wwwroot') {
                $record->value    = dropslash($value);
            }

951
952
953
954
955
956
957
958
959
            if (empty($value)) {
                delete_records('auth_instance_config', 'field', $field, 'instance', $values['instance']);
            } elseif ($values['create'] || !array_key_exists($field, $current)) {
                insert_record('auth_instance_config', $record);
            } else {
                update_record('auth_instance_config', $record, array('instance' => $values['instance'], 'field' => $field));
            }
        }

960
        db_commit();
961
962
963
        return $values;
    }

Donal McMullan's avatar
Donal McMullan committed
964
965
}

966
?>