lib.php 56.2 KB
Newer Older
1
2
3
4
5
<?php
/**
 *
 * @package    mahara
 * @subpackage auth
6
 * @author     Catalyst IT Ltd
7
8
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
9
10
11
 *
 */

12
13
defined('INTERNAL') || die();

14
function xmlrpc_exception (Exception $e) {
Donal McMullan's avatar
Donal McMullan committed
15
16
17
18
19
20
    if (($e instanceof XmlrpcServerException) && get_class($e) == 'XmlrpcServerException') {
        $e->handle_exception();
        return;
    } elseif (($e instanceof MaharaException) && get_class($e) == 'MaharaException') {
        throw new XmlrpcServerException($e->getMessage(), $e->getCode());
        return;
21
    }
Donal McMullan's avatar
Donal McMullan committed
22
23
    xmlrpc_error('An unexpected error has occurred: '.$e->getMessage(), $e->getCode());
    log_message($e->getMessage(), LOG_LEVEL_WARN, true, true, $e->getFile(), $e->getLine(), $e->getTrace());
24
25
}

26
function get_hostname_from_uri($uri = null) {
27
28
29
30
    static $cache = array();
    if (array_key_exists($uri, $cache)) {
        return $cache[$uri];
    }
31
    $count = preg_match("@^(?:http[s]?://)?([A-Z0-9\-\.]+).*@i", $uri, $matches);
32
    $cache[$uri] = $matches[1];
33
34
35
36
37
38
39
40
41
42
43
    if ($count > 0) return $matches[1];
    return false;
}

function dropslash($wwwroot) {
    if (substr($wwwroot, -1, 1) == '/') {
        return substr($wwwroot, 0, -1);
    }
    return $wwwroot;
}

44
45
46
47
48
49
50
function generate_token() {
    return sha1(str_shuffle('' . mt_rand(999999,99999999) . microtime(true)));
}

function start_jump_session($peer, $instanceid, $wantsurl="") {
    global $USER;

51
52
53
54
55
    if ($USER->get('parentuser')) {
        // Prevent masquerading users from jumping
        throw new AccessTotallyDeniedException(get_string('cannotjumpasmasqueradeduser', 'auth'));
    }

56
57
58
59
60
61
62
63
64
65
66
67
    $rpc_negotiation_timeout = 15;
    $providers = get_service_providers($USER->authinstance);

    $approved = false;
    foreach ($providers as $provider) {
        if ($provider['wwwroot'] == $peer->wwwroot) {
            $approved = true;
            break;
        }
    }

    if (false == $approved) {
Aaron Wells's avatar
Aaron Wells committed
68
        // This shouldn't happen: the user shouldn't have been presented with
Donal McMullan's avatar
Donal McMullan committed
69
        // the link
70
        throw new AccessTotallyDeniedException('Host not approved for sso');
71
72
73
74
    }

    // set up the session
    $sso_session = get_record('sso_session',
75
                              'userid',     $USER->id);
76
77
78
79
80
81
82
83
84
85
86
    if ($sso_session == false) {
        $sso_session = new stdClass();
        $sso_session->instanceid = $instanceid;
        $sso_session->userid = $USER->id;
        $sso_session->username = $USER->username;
        $sso_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
        $sso_session->token = generate_token();
        $sso_session->confirmtimeout = time() + $rpc_negotiation_timeout;
        $sso_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');
        $sso_session->sessionid = session_id();
        if (! insert_record('sso_session', $sso_session)) {
Donal McMullan's avatar
Donal McMullan committed
87
            throw new SQLException("database error");
88
89
90
91
        }
    } else {
        $sso_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
        $sso_session->token = generate_token();
92
        $sso_session->instanceid = $instanceid;
93
94
        $sso_session->confirmtimeout = time() + $rpc_negotiation_timeout;
        $sso_session->expires = time() + (integer)ini_get('session.gc_maxlifetime');
95
        $sso_session->useragent = sha1($_SERVER['HTTP_USER_AGENT']);
96
97
        $sso_session->sessionid = session_id();
        if (false == update_record('sso_session', $sso_session, array('userid' => $USER->id))) {
Donal McMullan's avatar
Donal McMullan committed
98
            throw new SQLException("database error");
99
100
101
        }
    }

102
    $wwwroot = dropslash(get_config('wwwroot'));
103
104
105
106
107
108
109

    // construct the redirection URL
    $url = "{$peer->wwwroot}{$peer->application->ssolandurl}?token={$sso_session->token}&idp={$wwwroot}&wantsurl={$wantsurl}";

    return $url;
}

110
function api_dummy_method($methodname, $argsarray, $functionname) {
111
112
113
    return call_user_func_array($functionname, $argsarray);
}

114
function find_remote_user($username, $wwwroot) {
115
    $authinstances = auth_get_auth_instances_for_wwwroot($wwwroot);
116

117
    $candidates = array();
118
119
120

    foreach ($authinstances as $authinstance) {
        if ($authinstance->authname != 'xmlrpc') {
121
            continue;
122
123
124
        }
        try {
            $user = new User;
125
            $user->find_by_instanceid_username($authinstance->id, $username, true);
126
            $candidates[$authinstance->id] = $user;
127
128
129
        } catch (Exception $e) {
            // we don't care
            continue;
130
        }
131
132
133
134
135
136
    }

    if (count($candidates) != 1) {
        return false;
    }

137
    safe_require('auth', 'xmlrpc');
138
    return array(end($candidates), new AuthXmlrpc(key($candidates)));
139
140
141
142
143
}

function fetch_user_image($username) {
    global $REMOTEWWWROOT;

144
145
    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
146
147
148
        return false;
    }

149
150
    $ic = $user->profileicon;
    if (!empty($ic)) {
151
        $filename = get_config('dataroot') . 'artefact/file/profileicons/' . ($user->profileicon % 256) . '/'.$user->profileicon;
152
153
154
155
156
157
158
159
160
161
        $return = array();
        try {
            $fi = file_get_contents($filename);
        } catch (Exception $e) {
            // meh
        }

        $return['f1'] = base64_encode($fi);

        require_once('file.php');
162
        $im = get_dataroot_image_path('artefact/file/profileicons' , $user->profileicon, 100);
163
164
165
166
        $fi = file_get_contents($im);
        $return['f2'] = base64_encode($fi);
        return $return;
    } else {
167
        // no icon
168
169
170
    }
}

171
172
173
174
175
176
177
/*
 * Check session and return user data for the provided token.
 *
 * @param  string $token    The unique ID provided by remotehost.
 * @param  string $seragent User Agent string (as seen by peer) - ignored
 * @return array  $userdata Array of user info for remote host
 */
178
function user_authorise($token, $useragent) {
179
    global $USER;
180

181
    $sso_session = get_record('sso_session', 'token', $token);
182
    if (empty($sso_session)) {
Donal McMullan's avatar
Donal McMullan committed
183
        throw new XmlrpcServerException('No such session exists');
184
185
186
187
    }

    // check session confirm timeout
    if ($sso_session->expires < time()) {
Donal McMullan's avatar
Donal McMullan committed
188
        throw new XmlrpcServerException('This session has timed out');
189
190
191
192
193
194
195
    }

    // session okay, try getting the user
    $user = new User();
    try {
        $user->find_by_id($sso_session->userid);
    } catch (Exception $e) {
Donal McMullan's avatar
Donal McMullan committed
196
        throw new XmlrpcServerException('Unable to get information for the specified user');
197
198
    }

199
200
    require_once(get_config('docroot') . 'artefact/lib.php');
    require_once(get_config('docroot') . 'artefact/internal/lib.php');
201
202
203
204
205
206
207

    $element_list = call_static_method('ArtefactTypeProfile', 'get_all_fields');
    $element_required = call_static_method('ArtefactTypeProfile', 'get_mandatory_fields');

    // load existing profile information
    $profilefields = array();
    $profile_data = get_records_select_assoc('artefact', "owner=? AND artefacttype IN (" . join(",",array_map(create_function('$a','return db_quote($a);'),array_keys($element_list))) . ")", array($USER->get('id')), '','artefacttype, title');
208
209
210
    if ($profile_data == false) {
        $profile_data = array();
    }
211
212

    $email = get_field('artefact_internal_profile_email', 'email', 'owner', $sso_session->userid, 'principal', 1);
213
    if (false == $email) {
Donal McMullan's avatar
Donal McMullan committed
214
        throw new XmlrpcServerException("No email adress for user");
215
216
217
218
    }

    $userdata = array();
    $userdata['username']                = $user->username;
219
    $userdata['email']                   = $email;
220
221
222
223
224
    $userdata['auth']                    = 'mnet';
    $userdata['confirmed']               = 1;
    $userdata['deleted']                 = 0;
    $userdata['firstname']               = $user->firstname;
    $userdata['lastname']                = $user->lastname;
225
226
    $userdata['city']                    = array_key_exists('city', $profile_data) ? $profile_data['city']->title : '';
    $userdata['country']                 = array_key_exists('country', $profile_data) ? $profile_data['country']->title : '';
227

228
    if (is_numeric($user->profileicon)) {
229
        $filename = get_config('dataroot') . 'artefact/file/profileicons/' . ($user->profileicon % 256) . '/'.$user->profileicon;
230
        if (file_exists($filename) && is_readable($filename)) {
231
            $userdata['imagehash'] = sha1_file($filename);
232
233
234
235
236
237
238
239
240
241
242
        }
    }

    get_service_providers($USER->authinstance);

    // Todo: push application name to list of hosts... update Moodle block to display more info, maybe in 'Other' list
    $userdata['myhosts'] = array();

    return $userdata;
}

243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/**
 * Retrieve a file for a user calling this function
 * The file is encoded in base64
 * @global object $REMOTEWWWROOT
 * @param string $username
 * @param integer $id Artefact to send
 * @return array The file content encoded in base 64 + file name
 */
function get_file($username, $id) {

    global $REMOTEWWWROOT;

    //check that the user exists
    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
        throw new ExportException("Could not find user $username for $REMOTEWWWROOT");
    }

    //check that the user is owner of the requested file
    safe_require('artefact', 'file');
    $file = artefact_instance_from_id($id);
    if (!record_exists('artefact', 'owner', $user->id, 'id', $id)) {
        throw new ExportException("You are not allowed to get this file.");
    }

    //retrieve the content and send the file encoded in base 64
    $filecontent = base64_encode(file_get_contents($file->get_path()));
    return array($filecontent, $file->name);
}


/**
 * Retrieve list of files/folders matching the search
 * @global object $REMOTEWWWROOT
 * @param string $username
 * @param string $search
 * @return array list of files/folders matching the search
 */
function search_folders_and_files($username, $search='') {

    global $REMOTEWWWROOT;

    //check that the user exists
    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
        throw new ExportException("Could not find user $username for $REMOTEWWWROOT");
    }

    $list = array();
    safe_require('artefact', 'file');
    $filetypes = array_diff(PluginArtefactFile::get_artefact_types(), array('profileicon'));
    foreach ($filetypes as $k => $v) {
        if ($v == 'folder') {
            unset($filetypes[$k]);
        }
    }
    $filetypesql = "('" . join("','", $filetypes) . "')";

    $ownersql = artefact_owner_sql($user->id);

    //retrieve folders and files of a specific Mahara folder
    $sql = "SELECT
                *
            FROM
                {artefact} a
            LEFT JOIN {artefact_tag} at ON (at.artefact = a.id)
            WHERE
                $ownersql
                AND
                (a.title like ? OR at.tag like ?)";
    $list =  array(

            'files'   => get_records_sql_array($sql." AND artefacttype IN $filetypesql ORDER BY title", array('%'.$search.'%','%'.$search.'%')),
            'folders' => get_records_sql_array($sql." AND artefacttype = 'folder' ORDER BY title", array('%'.$search.'%','%'.$search.'%'))
    );

    return $list;
}

/**
 * Retrieve file list in a folder
 * @global object $REMOTEWWWROOT
 * @param string $username
 * @param integer $folderid  folder to browse
 * @return array The complete folder path + list of files for a specific Mahara folder
 */
function get_folder_files($username, $folderid) {

    global $REMOTEWWWROOT;

    //check that the user exists
    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
        throw new ExportException("Could not find user $username for $REMOTEWWWROOT");
    }

    $list = array();
    safe_require('artefact', 'file');
    $filetypes = array_diff(PluginArtefactFile::get_artefact_types(), array('profileicon'));
    foreach ($filetypes as $k => $v) {
        if ($v == 'folder') {
            unset($filetypes[$k]);
        }
    }
    $filetypesql = "('" . join("','", $filetypes) . "')";

    $ownersql = artefact_owner_sql($user->id);

    $folderpath = array(); //the complete folder path (some client could need it)
    if (!empty($folderid)) {
        $pathsql = " AND parent = $folderid";

        //build the path
        $parentids = artefact_get_parents_for_cache($folderid); //the closest parent is on the first key
                                                            //the further parent is on the last key
        foreach ($parentids as $id => $dump) {
            $artefact = get_record('artefact', 'id', $id);
            array_unshift($folderpath, array('path' => $artefact->id, 'name' => $artefact->title));
        }

    } else {
        $pathsql = "AND parent IS NULL";
    }
    array_unshift($folderpath, array('path' => null, 'name' => 'Root'));

    //retrieve folders and files of a specific Mahara folder
    $list =  array(
            'files'   => get_records_select_array('artefact', "artefacttype IN $filetypesql AND $ownersql $pathsql", array(),'title'),
            'folders' => get_records_select_array('artefact', "artefacttype = 'folder' AND $ownersql $pathsql", array(),'title')
    );

    return array($folderpath, $list);
}

377
378
function send_content_intent($username) {
    global $REMOTEWWWROOT;
379
    require_once(get_config('docroot') . 'import/lib.php');
380

381
382
    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
383
        throw new ImportException(null, "Could not find user $username for $REMOTEWWWROOT");
384
385
    }

386
    if (!$authinstance->weimportcontent) {
387
        $e = new ImportException(null, 'Importing content is disabled');
388
389
        $e->set_log_off(); // we don't want these ones.
        throw $e;
390
391
    }

392
    $queue = PluginImport::create_new_queue($user->id, null, $REMOTEWWWROOT, 0);
393
394

    return array(
395
        'sendtype' => (($queue->queue) ? 'queue' : 'immediate'),
396
397
398
399
        'token' => $queue->token,
    );
}

400
function send_content_ready($token, $username, $format, $importdata, $fetchnow=false) {
401
    global $REMOTEWWWROOT;
402
    require_once(get_config('docroot') . 'import/lib.php');
403

404
405
    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
406
        throw new ImportException(null, "Could not find user $username for $REMOTEWWWROOT");
407
408
409
410
    }

    // go verify the token
    if (!$queue = get_record('import_queue', 'token', $token, 'host', $REMOTEWWWROOT)) {
411
        throw new ImportException(null, "Could not find queue record with given token for username $username for $REMOTEWWWROOT");
412
413
414
    }

    if (strtotime($queue->expirytime) < time()) {
415
        throw new ImportException(null, "Queue record has expired");
416
417
    }

418
419
420
421
422
423
424
    $class = null;
    try {
        $class = PluginImport::class_from_format($format);
    } catch (Exception $e) {
        throw new ImportException(null, "Invalid format $format");
    }

425
    $queue->format = $format;
426
427
428
429
    if ($class == 'PluginImportLeap') {
        // don't import persondata over mnet
        // because it will just silently overwrite stuff
        // which is not really desirable.
430
        $queue->loglevel = get_config('leapovermnetloglevel');
431
432
        $importdata['skippersondata'] = true;
    }
433
434
435
    $queue->data = serialize($importdata);
    update_record('import_queue', $queue);
    $tr = new MnetImporterTransport($queue);
436
    try {
437
        $tr->validate_import_data();
438
    } catch (Exception $e) {
439
        throw new ImportException(null, 'Invalid importdata: ' . $e->getMessage());
440
    }
441
442


443

444
    if (!array_key_exists('totalsize', $importdata)) {
445
        throw new ImportException(null, 'Invalid importdata: missing totalsize');
446
447
448
    }

    if (!$user->quota_allowed($importdata['totalsize'])) {
449
        $e = new ImportException(null, 'Exceeded user quota');
450
451
452
453
        $e->set_log_off();
        throw $e;
    }

454
455

    $result = new StdClass;
456
    if ($fetchnow && PluginImport::import_immediately_allowed()) {
457
        // either immediately spawn a curl request to go fetch the file
458
        $importer = PluginImport::create_importer($queue->id, $tr, $queue);
459
        $importer->prepare();
460
461
462
463
464
        try {
            $importer->validate_transported_data($tr);
        } catch (Exception $e) {
            throw new ImportException(null, 'Invalid importdata: ' . $e->getMessage());
        }
465
        $importer->process();
466
        $importer->cleanup();
467
        delete_records('import_queue', 'id', $queue->id);
468
469
        $result->status = true;
        $result->type = 'complete';
470
471
472
473
474
        $returndata = $importer->get_return_data();
        $result->querystring = '?';
        foreach ($importer->get_return_data() as $k => $v) {
            $result->querystring .= $k . '=' . $v . '&';
        }
475
        $importer->get('importertransport')->cleanup();
476
477
478
479
480
481
482
483
    } else {
        // or set ready to 1 for the next cronjob to go fetch it.
        $result->status = set_field('import_queue', 'ready', 1, 'id', $queue->id);
        $result->type = 'queued';
    }
    return $result;
}

484
/**
Aaron Wells's avatar
Aaron Wells committed
485
 * If we're an IDP, kill_children will kill the session of the given user here,
486
487
 * as well as at any other children
 *
Aaron Wells's avatar
Aaron Wells committed
488
 * NOTE: well, currently it doesn't call kill_child on any other children, but
489
490
491
 * it will kill the local sessions for the user
 *
 * @param   string  $username       Username for session to kill
492
 * @param   string  $useragent      SHA1 hash of user agent from peer - ignored
493
494
495
496
497
498
 * @return  string                  A plaintext report of what has happened
 */
function kill_children($username, $useragent) {
    global $REMOTEWWWROOT; // comes from server.php
    //require_once(get_config('docroot') .'api/xmlrpc/client.php');

Aaron Wells's avatar
Aaron Wells committed
499
    // We've received a logout request for user X. In Mahara, usernames are unique. So we check that user X
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
    // has an authinstance that would have been able to SSO to the remote site.
    $userid = get_field('usr', 'id', 'username', $username);
    $providers = get_service_providers(get_field('usr', 'authinstance', 'username', $username));

    $approved = false;
    foreach ($providers as $provider) {
        if ($provider['wwwroot'] == $REMOTEWWWROOT) {
            $approved = true;
            break;
        }
    }

    if (false == $approved) {
        return 'This host is not permitted to kill sessions for this username';
    }

516
    $mnetsessions = get_records_select_array('sso_session', 'userid = ?', array($userid));
517
518
519
520
521
522
523

    // Prepare to destroy local sessions associated with the user
    $start = ob_start();
    $uc = ini_get('session.use_cookies');
    ini_set('session.use_cookies', false);
    $sesscache = isset($_SESSION) ? clone($_SESSION) : null;
    $sessidcache = session_id();
524
    /* session_write_close(); No longer needed - already closed */
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
    unset($_SESSION);

    foreach($mnetsessions as $mnetsession) {
        // Kills all local sessions associated with this user
        // TODO: We should send kill_child requests to the remote servers too
        session_id($mnetsession->sessionid);
        session_start();
        unset($_SESSION);
        $_SESSION = array();
        session_destroy();
        session_write_close();
    }

    // We're done destroying local sessions
    ini_set('session.use_cookies', $uc);
    if ($sessidcache) {
        session_name(get_config('cookieprefix') . 'mahara');
        session_id($sessidcache);
        session_start();
        $_SESSION = ($sesscache) ? clone($sesscache) : null;
        session_write_close();
    }
    $end = ob_end_clean();

    delete_records('sso_session',
                   'userid',    $userid);

552
    clear_duplicate_cookies();
553
554
555
    return true;
}

556
557
558
559
function xmlrpc_not_implemented() {
    return true;
}

560
561
562
563
564
565
566
567
function get_views_for_user($username, $query=null) {
    global $REMOTEWWWROOT, $USER;

    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
        return false;
    }

568
    $USER->reanimate($user->id, $authinstance->instanceid);
569
    require_once('view.php');
570
571
572
573
574
575
576
577
578
579
580
581
    $data = View::view_search($query, null, (object) array('owner' => $USER->get('id')), null, null, 0, true, null, null, true);
    require_once('collection.php');
    $data->collections = Collection::get_mycollections_data(0, 0, $USER->get('id'));
    foreach ($data->collections->data as $c) {
        $cobj = new Collection($c->id);
        if ($c->numviews > 0) {
            $c->url = $cobj->get_url();
        }
        else {
            $c->url = '';
        }
    }
582
583
584
585
    $data->displayname = display_name($user);
    return $data;
}

586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
function get_groups_for_user($username) {
    global $REMOTEWWWROOT, $USER;

    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
        return false;
    }

    require_once('group.php');
    $USER->reanimate($user->id, $authinstance->instanceid);
    $groupdata = group_get_associated_groups($USER->get('id'), 'all', null, null);
    $data = new stdclass();
    $data->data = array();
    $data->count = $groupdata['count'];
    $data->displayname = display_name($user);
    if ($data->count) {
        foreach ($groupdata['groups'] as $g) {
603
            $groupurl = group_homepage_url($g, false);
604
605
606
607
608
609
610
611
612
            $record = array();
            $record['id'] = $g->id;
            $record['name'] = $g->name;
            $record['description'] = $g->description;
            $record['public'] = $g->public;
            $record['jointype'] = $g->jointype;
            $record['grouptype'] = $g->grouptype;
            $record['membershiptype'] = $g->membershiptype;
            $record['role'] = $g->role;
613
614
            $record['url'] = '/' . $groupurl;
            $record['fullurl'] = get_config('wwwroot') . $groupurl;
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
            $data->data[] = $record;
        }
    }
    return $data;
}

function get_notifications_for_user($username, $notificationtypes, $maxitems) {
    global $REMOTEWWWROOT, $USER;

    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
        return false;
    }

    $USER->reanimate($user->id, $authinstance->instanceid);

    $sql = "SELECT n.id, n.subject, n.message, n.url, n.urltext, n.read, t.name AS type
              FROM {notification_internal_activity} n JOIN {activity_type} t ON n.type = t.id
             WHERE n.usr = ? AND t.name IN (" . join(',', array_fill(0, count($notificationtypes), '?')) . ")
          ORDER BY n.ctime DESC
             LIMIT ?";

    $params = array($USER->get('id'));
    $params = array_merge($params, $notificationtypes);
    $params[] = $maxitems;
    $records = get_records_sql_array($sql, $params);

    $data = new stdclass;
    $data->data = $records;
    $data->count = count($records);
    $data->displayname = display_name($user);
    return $data;
}

function get_watchlist_for_user($username, $maxitems) {

    global $REMOTEWWWROOT, $USER;

    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
        return false;
    }

    $USER->reanimate($user->id, $authinstance->instanceid);

    $sql = 'SELECT v.title AS viewtitle, v.id AS viewid, g.name AS groupname, u.*
              FROM {view} v
              JOIN {usr_watchlist_view} wv ON wv.view = v.id
         LEFT JOIN {usr} u ON u.id = v.owner
         LEFT JOIN {group} g ON g.id = v.group
             WHERE wv.usr = ?
          ORDER BY v.title
             LIMIT ?';

    $records = get_records_sql_array($sql, array($USER->get('id'), $maxitems));

    $data = new stdclass;
    $data->data = array();
    $data->count = count($records);
    $data->displayname = display_name($user);
    foreach ($records as $r) {
        $v = array();
        $v['id'] = $r->viewid;
        $v['title'] = $r->viewtitle;
        $v['group'] = $r->groupname;
        $v['author'] = $r->id ? display_name($r) : '';
        $v['url'] = '/view/view.php?id=' . $r->viewid;
        $v['fullurl'] = get_config('wwwroot') . 'view/view.php?id=' . $r->viewid;
        $data->data[] = $v;
    }
    return $data;
}

688
689
690
691
692
693
694
695
696
/**
 * Submits a view or collection for assessment by a remote service
 *
 * @param string $username
 * @param int $id The ID of the view or collection to be submitted
 * @param boolean $iscollection Indicates whether it's a view or a collection
 * @return array An array of data for the web service to consume
 */
function submit_view_for_assessment($username, $id, $iscollection = false) {
697
698
699
700
701
702
703
    global $REMOTEWWWROOT;

    list ($user, $authinstance) = find_remote_user($username, $REMOTEWWWROOT);
    if (!$user) {
        return false;
    }

704
705
    $id = (int) $id;
    if (!$id) {
706
707
708
709
        return false;
    }

    require_once('view.php');
710
711
    $remotehost = $authinstance->config['wwwroot'];
    $userid = $user->get('id');
712

713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
    db_begin();
    if ($iscollection) {
        require_once('collection.php');
        $collection = new Collection($id);
        $title = $collection->get('name');
        $description = $collection->get('description');

        // Check whether the collection is already submitted
        if ($collection->is_submitted()) {
            // If this is already submitted to something else, throw an exception
            if ($collection->get('submittedgroup') || $collection->get('submittedhost') !== $REMOTEWWWROOT) {
                throw new CollectionSubmissionException(get_string('collectionalreadysubmitted', 'view'));
            }

            // It may have been submitted to a different assignment in the same remote
            // site, but there's no way we can tell. So we'll just send the access token
            // back.
            $access = $collection->get_invisible_token();
        }
        else {
            $collection->submit(null, $remotehost, $userid);
            $access = $collection->new_token(false);
        }
736

737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
        // If the collection is empty, $access will be false
        if (!$access) {
            throw new CollectionSubmissionException(get_string('cantsubmitemptycollection', 'view'));
        }
    }
    else {
        $view = new View($id);
        $title = $view->get('title');
        $description = $view->get('description');

        if ($view->is_submitted()) {
            // If this is already submitted to something else, throw an exception
            if ($view->get('submittedgroup') || $view->get('submittedhost') !== $REMOTEWWWROOT) {
                throw new ViewSubmissionException(get_string('viewalreadysubmitted', 'view'));
            }

            // It may have been submitted to a different assignment in the same remote
            // site, but there's no way we can tell. So we'll just send the access token
            // back.
            $access = View::get_invisible_token($id);
        }
        else {
            View::_db_submit(array($id), null, $remotehost, $userid);
            $access = View::new_token($id, false);
        }
    }
763
764

    $data = array(
765
766
767
768
769
        'id'          => $id,
        'title'       => $title,
        'description' => $description,
        'fullurl'     => get_config('wwwroot') . 'view/view.php?mt=' . $access->token,
        'url'         => '/view/view.php?mt=' . $access->token,
770
771
772
        'accesskey'   => $access->token,
    );

773
774
    // Provide each artefact plugin the opportunity to handle the remote submission and
    // provide return data for the webservice caller
775
776
777
778
    foreach (plugins_installed('artefact') as $plugin) {
        safe_require('artefact', $plugin->name);
        $classname = generate_class_name('artefact', $plugin->name);
        if (is_callable($classname . '::view_submit_external_data')) {
779
            $data[$plugin->name] = call_static_method($classname, 'view_submit_external_data', $id, $iscollection);
780
781
        }
    }
782
    db_commit();
783

784
785
786
    return $data;
}

787
788
789
790
791
792
793
794
/**
 * Releases a submission to a remote host.
 * @param int $id A view or collection id
 * @param mixed $assessmentdata Assessment data from the remote host, for this assignment
 * @param string $teacherusername The username of the teacher who is releasing the assignment
 * @param boolean $iscollection Whether the $id is a view or a collection
 */
function release_submitted_view($id, $assessmentdata, $teacherusername, $iscollection = false) {
795
    global $REMOTEWWWROOT, $USER;
796
    list ($teacher, $authinstance) = find_remote_user($teacherusername, $REMOTEWWWROOT);
797
798
799
800

    require_once('view.php');

    db_begin();
801
802
803
804
805
806
807
808
809
810
811
    if ($iscollection) {
        require_once('collection.php');
        $collection = new Collection($id);
        $collection->release($teacher);
    }
    else {
        $view = new View($id);
        View::_db_release(array($id), $view->get('owner'));
    }

    // Provide each artefact plugin the opportunity to handle the remote submission release
812
813
814
815
    foreach (plugins_installed('artefact') as $plugin) {
        safe_require('artefact', $plugin->name);
        $classname = generate_class_name('artefact', $plugin->name);
        if (is_callable($classname . '::view_release_external_data')) {
816
            call_static_method($classname, 'view_release_external_data', $id, $assessmentdata, $teacher ? $teacher->id : 0, $iscollection);
817
818
819
820
821
822
823
        }
    }

    // Release the view for editing
    db_commit();
}

824
825
826
827
828
829
830
/**
 * Given a USER, get all Service Providers for that User, based on child auth
 * instances of its canonical auth instance
 */
function get_service_providers($instance) {
    static $cache = array();

831
832
833
834
    if (defined('INSTALLER')) {
        return array();
    }

835
836
837
838
    if (array_key_exists($instance, $cache)) {
        return $cache[$instance];
    }

839
    $query = "
840
841
842
843
844
845
        SELECT
            h.name,
            a.ssolandurl,
            h.wwwroot,
            aic.instance
        FROM
846
847
848
849
850
            {auth_instance_config} aic,
            {auth_instance_config} aic2,
            {auth_instance_config} aic3,
            {host} h,
            {application} a
851
        WHERE
852
853
          ((aic.value = '1' AND
            aic.field = 'theyautocreateusers' ) OR
854
           (aic.value = ?  AND
855
            aic.field = 'parent')) AND
856
857

            aic.instance = aic2.instance AND
858
            aic2.field = 'wwwroot' AND
859
860
861
            aic2.value = h.wwwroot AND

            aic.instance = aic3.instance AND
862
863
            aic3.field = 'wessoout' AND
            aic3.value = '1' AND
864

865
            a.name = h.appname";
866
    try {
867
        $results = get_records_sql_assoc($query, array($instance));
868
869
870
871
    } catch (SQLException $e) {
        // Table doesn't exist yet
        return array();
    }
872
873
874
875
876
877
878
879
880
881
882
883
884

    if (false == $results) {
        $results = array();
    }

    foreach($results as $key => $result) {
        $results[$key] = get_object_vars($result);
    }

    $cache[$instance] = $results;
    return $cache[$instance];
}

885
function get_public_key($uri, $application=null) {
886

887
888
889
890
891
892
893
    static $keyarray = array();
    if (isset($keyarray[$uri])) {
        return $keyarray[$uri];
    }

    $openssl = OpenSslRepo::singleton();

894
    if (empty($application)) {
895
        $application = 'moodle';
896
    }
897

898
    $xmlrpcserverurl = get_field('application', 'xmlrpcserverurl', 'name', $application);
899
900
    if (empty($xmlrpcserverurl)) {
        throw new XmlrpcClientException('Unknown application');
Aaron Wells's avatar
Aaron Wells committed
901
    }
902
    $wwwroot = dropslash(get_config('wwwroot'));
903

904
    $rq = xmlrpc_encode_request('system/keyswap', array($wwwroot, $openssl->certificate), array("encoding" => "utf-8"));
905

906
907
908
909
910
911
    $config = array(
        CURLOPT_URL => $uri . $xmlrpcserverurl,
        CURLOPT_POST => true,
        CURLOPT_USERAGENT => 'Moodle',
        CURLOPT_POSTFIELDS => $rq,
        CURLOPT_HTTPHEADER => array("Content-Type: text/xml charset=UTF-8", 'Expect: '),
912
913
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => 0,
914
915
    );

916
    $result = mahara_http_request($config);
917

918
919
920
    if (!empty($result->errno)) {
        throw new XmlrpcClientException('Curl error: ' . $result->errno . ': ' . $result->error);
    }
921
    if (empty($result->data)) {
922
923
        throw new XmlrpcClientException('CURL connection failed');
    }
924

925
    $response_code        = $result->info['http_code'];
926
927
928
929
930
    $response_code_prefix = substr($response_code, 0, 1);

    if ('2' != $response_code_prefix) {
        if ('4' == $response_code_prefix) {
            throw new XmlrpcClientException('Client error code: ', $response_code);
931
        } elseif ('5' == $response_code_prefix) {
932
933
934
935
            throw new XmlrpcClientException('An error occurred at the remote server. Code: ', $response_code);
        }
    }

936
    $res = xmlrpc_decode($result->data);
937

938
939
940
    // XMLRPC error messages are returned as an array
    // We are expecting a string
    if (!is_array($res)) {
941
942
943
944
945
946
947
948
        $keyarray[$uri] = $res;
        $credentials=array();
        if (strlen(trim($keyarray[$uri]))) {
            $credentials = openssl_x509_parse($keyarray[$uri]);
            $host = $credentials['subject']['CN'];
            if (strpos($uri, $host) !== false) {
                return $keyarray[$uri];
            }
949
            throw new XmlrpcClientException('The remote site sent us a key that is valid for ' . $host . ' instead of their hostname (' . $uri . ')', 500);
950
        }
951
952
    } else {
        throw new XmlrpcClientException($res['faultString'], $res['faultCode']);
953
954
955
956
    }
    return false;
}

957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
/**
 * Output a valid XML-RPC error message.
 *
 * @param  string   $message              The error message
 * @param  int      $code                 Unique identifying integer
 * @return string                         An XMLRPC error doc
 */
function xmlrpc_error($message, $code) {
    echo <<<EOF
<?xml version="1.0"?>
<methodResponse>
   <fault>
      <value>
         <struct>
            <member>
               <name>faultCode</name>
               <value><int>$code</int></value>
            </member>
            <member>
               <name>faultString</name>
               <value><string>$message</string></value>
            </member>
         </struct>
      </value>
   </fault>
</methodResponse>
EOF;
}

986
function xmlenc_envelope_strip(&$xml, $oldkeyok=false) {
987
988
989
990
991
    $openssl           = OpenSslRepo::singleton();
    $payload_encrypted = true;
    $data              = base64_decode($xml->EncryptedData->CipherData->CipherValue);
    $key               = base64_decode($xml->EncryptedKey->CipherData->CipherValue);
    $payload           = '';    // Initialize payload var
992
    $payload           = $openssl->openssl_open($data, $key, $oldkeyok);
993
994
995
996
997
998
999
1000
1001
    $xml               = parse_payload($payload);
    return $payload;
}

function parse_payload($payload) {
    try {
        $xml = new SimpleXMLElement($payload);
        return $xml;
    } catch (Exception $e) {
Donal McMullan's avatar
Donal McMullan committed
1002
        throw new MaharaException('Encrypted payload is not a valid XML document', 6002);
1003
1004
1005
    }
}

1006
function get_peer($wwwroot, $cache=true) {
1007

1008
1009
    $wwwroot = (string)$wwwroot;
    static $peers = array();
1010
1011
1012
    if ($cache) {
        if (isset($peers[$wwwroot])) return $peers[$wwwroot];
    }
1013

1014
    require_once(get_config('libroot') . 'peer.php');
1015
    $peer = new Peer();
1016

1017
    if (!$peer->findByWwwroot($wwwroot)) {
1018
        // Bootstrap unknown hosts?
1019
        throw new MaharaException("We don't have a record for your webserver ($wwwroot) in our database", 6003);
1020
1021
1022
1023
1024
    }
    $peers[$wwwroot] = $peer;
    return $peers[$wwwroot];
}

1025
1026
function get_peer_from_instanceid($authinstanceid) {
    $sql = 'SELECT
1027
                h.wwwroot, h.name
1028
            FROM
1029
                {auth_instance_config} aic,
1030
1031
                {host} h
            WHERE
1032
1033
                aic.value = h.wwwroot AND
                aic.instance = ? AND aic.field = \'wwwroot\'';
1034
1035
1036
    return get_record_sql($sql, array($authinstanceid));
}

1037
1038
1039
/**
 * Check that the signature has been signed by the remote host.
 */
1040
function xmldsig_envelope_strip(&$xml) {
1041

1042
1043
1044
1045
1046
1047
    $signature      = base64_decode($xml->Signature->SignatureValue);
    $payload        = base64_decode($xml->object);
    $wwwroot        = (string)$xml->wwwroot;
    $timestamp      = $xml->timestamp;
    $peer           = get_peer($wwwroot);

1048

1049
1050
1051
    // Does the signature match the data and the public cert?
    $signature_verified = openssl_verify($payload, $signature, $peer->certificate);

1052
    if ($signature_verified == 0) {
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
        // Maybe the remote host is using a new key?
        // Make a dummy request so we'll be given a new key
        log_info("Signature verification for message from $wwwroot failed, checking to see if they have a new signature for us");
        require_once(get_config('docroot') . 'api/xmlrpc/client.php');
        $client = new Client();
        $client->set_method('system/listServices')
               ->send($wwwroot);

        // Now use the new key and re-try verification
        $peer = get_peer($wwwroot, false);
        $signature_verified = openssl_verify($payload, $signature, $peer->certificate);
1064
1065
1066
1067
1068
1069
1070
1071
1072
    }

    if ($signature_verified == 1) {
        // Parse the XML
        try {
            $xml = new SimpleXMLElement($payload);
            return $payload;
        } catch (Exception $e) {
            throw new MaharaException('Signed payload is not a valid XML document', 6007);
1073
1074
1075
1076
        }
    }

    throw new MaharaException('An error occurred while trying to verify your message signature', 6004);
1077
1078
}

1079
1080
1081
1082
/**
 * Encrypt a message and return it in an XML-Encrypted document
 *
 * This function can encrypt any content, but it was written to provide a system
Aaron Wells's avatar
Aaron Wells committed
1083
 * of encrypting XML-RPC request and response messages. The message does not
1084
 * need to be text - binary data should work.
Aaron Wells's avatar
Aaron Wells committed
1085
1086
1087
 *
 * Asymmetric keys can encrypt only small chunks of data. Usually 1023 or 2047
 * characters, depending on the key size. So - we generate a symmetric key and
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
 * use the asymmetric key to secure it for transport with the data.
 *
 * We generate a symmetric key
 * We encrypt the symmetric key with the public key of the remote host
 * We encrypt our content with the symmetric key
 * We base64 the key & message data.
 * We identify our wwwroot - this must match our certificate's CN
 *
 * Normally, the XML-RPC document will be parceled inside an XML-SIG envelope.
 * We parcel the XML-SIG document inside an XML-ENC envelope.
 *
 * See the {@Link http://www.w3.org/TR/xmlenc-core/ XML-ENC spec} at the W3c
 * site
 *
 * @param  string   $message              The data you want to sign
 * @param  string   $remote_certificate   Peer's certificate in PEM format
 * @return string                         An XML-ENC document
 */
function xmlenc_envelope($message, $remote_certificate) {

    // Generate a key resource from the remote_certificate text string
    $publickey = openssl_get_publickey($remote_certificate);

    if ( gettype($publickey) != 'resource' ) {
        // Remote certificate is faulty.
Donal McMullan's avatar
Donal McMullan committed
1113
        throw new MaharaException('Could not generate public key resource from certificate', 1);
1114
1115
1116
    }

    // Initialize vars
1117
    $wwwroot = dropslash(get_config('wwwroot'));
1118
1119
1120
1121
1122
1123
1124
    $encryptedstring = '';
    $symmetric_keys = array();

    //      passed by ref ->      &$encryptedstring &$symmetric_keys
    $bool = openssl_seal($message, $encryptedstring, $symmetric_keys, array($publickey));
    $message = base64_encode($encryptedstring);
    $symmetrickey = base64_encode(array_pop($symmetric_keys));
1125
    $zed = 'nothing';
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152

    return <<<EOF
<?xml version="1.0" encoding="iso-8859-1"?>
    <encryptedMessage>
        <EncryptedData Id="ED" xmlns="http://www.w3.org/2001/04/xmlenc#">
            <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#arcfour"/>
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:RetrievalMethod URI="#EK" Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/>
                <ds:KeyName>XMLENC</ds:KeyName>
            </ds:KeyInfo>
            <CipherData>
                <CipherValue>$message</CipherValue>
            </CipherData>
        </EncryptedData>
        <EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#">
            <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:KeyName>SSLKEY</ds:KeyName>
            </ds:KeyInfo>
            <CipherData>
                <CipherValue>$symmetrickey</CipherValue>
            </CipherData>
            <ReferenceList>
                <DataReference URI="#ED"/>
            </ReferenceList>
            <CarriedKeyName>XMLENC</CarriedKeyName>
        </EncryptedKey>
1153
        <wwwroot>{$wwwroot}</wwwroot>
1154
        <X1>$zed</X1>
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
    </encryptedMessage>
EOF;
}

/**
 * Sign a message and return it in an XML-Signature document
 *
 * This function can sign any content, but it was written to provide a system of
 * signing XML-RPC request and response messages. The message will be base64
 * encoded, so it does not need to be text.
 *
 * We compute the SHA1 digest of the message.
 * We compute a signature on that digest with our private key.
 * We link to the public key that can be used to verify our signature.
 * We base64 the message data.
 * We identify our wwwroot - this must match our certificate's CN
 *
 * The XML-RPC document will be parceled inside an XML-SIG document, which holds
 * the base64_encoded XML as an object, the SHA1 digest of that document, and a
 * signature of that document using the local private key. This signature will
 * uniquely identify the RPC document as having come from this server.
 *
 * See the {@Link http://www.w3.org/TR/xmldsig-core/ XML-DSig spec} at the W3c
 * site
 *
 * @param  string   $message              The data you want to sign
 * @return string                         An XML-DSig document
 */
function xmldsig_envelope($message) {
1184

1185
    $openssl = OpenSslRepo::singleton();
1186
    $wwwroot = dropslash(get_config('wwwroot'));
1187
    $digest = sha1($message);
1188

1189
    $sig = base64_encode($openssl->sign_message($message));
1190
1191
    $message = base64_encode($message);
    $time = time();
1192
    // TODO: Provide RESTful access to our public key as per KeyInfo element
1193
1194
1195
1196
1197
1198
1199

return <<<EOF
<?xml version="1.0" encoding="iso-8859-1"?>
    <signedMessage>
        <Signature Id="MoodleSignature" xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
1200
                <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
1201
1202
1203
1204
1205
1206
1207
                <Reference URI="#XMLRPC-MSG">
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <DigestValue>$digest</DigestValue>
                </Reference>
            </SignedInfo>
            <SignatureValue>$sig</SignatureValue>
            <KeyInfo>
1208
                <RetrievalMethod URI="{$wwwroot}/api/xmlrpc/publickey.php"/>
1209
1210
1211
            </KeyInfo>
        </Signature>
        <object ID="XMLRPC-MSG">$message</object>
1212
        <wwwroot>{$wwwroot}</wwwroot>
1213
1214
1215
1216
1217
1218
        <timestamp>$time</timestamp>
    </signedMessage>
EOF;

}

1219
1220
1221
/**
 * Good candidate to be a singleton
 */
1222
class OpenSslRepo {
1223
1224
1225

    private $keypair = array();

1226
1227
1228
1229
1230
1231
1232
1233
    /**
     * Sign a message with our private key so that peers can verify that it came
     * from us.
     *
     * @param  string   $message
     * @return string
     * @access public
     */
1234
1235
1236
1237
1238
    public function sign_message($message) {
        $signature = '';
        $bool      = openssl_sign($message, $signature, $this->keypair['privatekey']);
        return $signature;
    }
1239

1240
    /**
Aaron Wells's avatar
Aaron Wells committed
1241
     * Decrypt some data using our private key and an auxiliary symmetric key.
1242
1243
     * The symmetric key encrypted the data, and then was itself encrypted with
     * our public key.
Aaron Wells's avatar
Aaron Wells committed
1244
     * This is because asymmetric keys can only safely be used to encrypt
1245
1246
1247
1248
     * relatively short messages.
     *
     * @param string   $data
     * @param string   $key
Aaron Wells's avatar
Aaron Wells committed
1249
1250
     * @param bool     $oldkeyok If true, we will simply return the data rather
     *                           than complaining about the key being old (if
1251
     *                           we could decrypt it with an older key)
1252
1253
1254
     * @return string
     * @access public
     */
1255
    public function openssl_open($data, $key, $oldkeyok=false) {
1256
1257
1258
1259
1260
1261
1262
        $payload = '';
        $isOpen = openssl_open($data, $payload, $key, $this->keypair['privatekey']);

        if (!empty($isOpen)) {
            return $payload;
        } else {
            // Decryption failed... let's try our archived keys
1263
            $openssl_history = $this->get_history();
1264
1265
1266
1267
1268
            foreach($openssl_history as $keyset) {
                $keyresource = openssl_pkey_get_private($keyset['keypair_PEM']);
                $isOpen      = openssl_open($data, $payload, $key, $keyresource);
                if ($isOpen) {
                    // It's an older code, sir, but it checks out
1269
1270
1271
1272
1273
1274
1275
                    if ($oldkeyok) {
                        return $payload;
                    }
                    else {
                        // We notify the remote host that the key has changed
                        throw new CryptException($this->keypair['certificate'], 7025);
                    }
1276
1277
1278
                }
            }
        }
1279
        throw new CryptException('We know nothing about the key used to encrypt this message', 7025);
1280
1281
    }

1282
    /**
1283
     * Singleton function keeps us from generating multiple instances of this
1284
1285
1286
1287
1288
     * class
     *
     * @return object   The class instance
     * @access public
     */
1289
1290
1291
1292
1293
    public static function singleton() {
        //single instance
        static $instance;

        //if we don't have the single instance, create one
1294
        if (!isset($instance)) {
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
            $instance = new OpenSslRepo();
        }
        return($instance);
    }

    /**
     * This is a singleton - don't try to create an instance by doing:
     * $openssl = new OpenSslRepo();
     * Instead, use:
     * $openssl = OpenSslRepo::singleton();
Aaron Wells's avatar
Aaron Wells committed
1305
     *
1306
1307
     */
    private function __construct() {
1308