MDL-16180 - add portfolio support to mnet
[moodle.git] / mnet / lib.php
1 <?php // $Id$
2 /**
3  * Library functions for mnet
4  *
5  * @author  Donal McMullan  donal@catalyst.net.nz
6  * @version 0.0.1
7  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8  * @package mnet
9  */
10 require_once $CFG->dirroot.'/mnet/xmlrpc/xmlparser.php';
11 require_once $CFG->dirroot.'/mnet/peer.php';
12 require_once $CFG->dirroot.'/mnet/environment.php';
14 /// CONSTANTS ///////////////////////////////////////////////////////////
16 define('RPC_OK',                0);
17 define('RPC_NOSUCHFILE',        1);
18 define('RPC_NOSUCHCLASS',       2);
19 define('RPC_NOSUCHFUNCTION',    3);
20 define('RPC_FORBIDDENFUNCTION', 4);
21 define('RPC_NOSUCHMETHOD',      5);
22 define('RPC_FORBIDDENMETHOD',   6);
24 $MNET = new mnet_environment();
25 $MNET->init();
27 /**
28  * Strip extraneous detail from a URL or URI and return the hostname
29  *
30  * @param  string  $uri  The URI of a file on the remote computer, optionally
31  *                       including its http:// prefix like
32  *                       http://www.example.com/index.html
33  * @return string        Just the hostname
34  */
35 function mnet_get_hostname_from_uri($uri = null) {
36     $count = preg_match("@^(?:http[s]?://)?([A-Z0-9\-\.]+).*@i", $uri, $matches);
37     if ($count > 0) return $matches[1];
38     return false;
39 }
41 /**
42  * Get the remote machine's SSL Cert
43  *
44  * @param  string  $uri     The URI of a file on the remote computer, including
45  *                          its http:// or https:// prefix
46  * @return string           A PEM formatted SSL Certificate.
47  */
48 function mnet_get_public_key($uri, $application=null) {
49     global $CFG, $MNET, $DB;
50     // The key may be cached in the mnet_set_public_key function...
51     // check this first
52     $key = mnet_set_public_key($uri);
53     if ($key != false) {
54         return $key;
55     }
57     if (empty($application)) {
58         $application = $DB->get_record('mnet_application', array('name'=>'moodle'));
59     }
61     $rq = xmlrpc_encode_request('system/keyswap', array($CFG->wwwroot, $MNET->public_key, $application->name), array("encoding" => "utf-8"));
62     $ch = curl_init($uri . $application->xmlrpc_server_url);
64     curl_setopt($ch, CURLOPT_TIMEOUT, 60);
65     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
66     curl_setopt($ch, CURLOPT_POST, true);
67     curl_setopt($ch, CURLOPT_USERAGENT, 'Moodle');
68     curl_setopt($ch, CURLOPT_POSTFIELDS, $rq);
69     curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml charset=UTF-8"));
70     curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
71     curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
73     // check for proxy
74     if (!empty($CFG->proxyhost) and !is_proxybypass($uri)) {
75         // SOCKS supported in PHP5 only
76         if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
77             if (defined('CURLPROXY_SOCKS5')) {
78                 curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
79             } else {
80                 curl_close($ch);
81                 print_error( 'socksnotsupported','mnet' );
82             }
83         }
85         curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
87         if (empty($CFG->proxyport)) {
88             curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
89         } else {
90             curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
91         }
93         if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
94             curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
95             if (defined('CURLOPT_PROXYAUTH')) {
96                 // any proxy authentication if PHP 5.1
97                 curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
98             }
99         }
100     }
102     $res = xmlrpc_decode(curl_exec($ch));
104     // check for curl errors
105     $curlerrno = curl_errno($ch);
106     if ($curlerrno!=0) {
107         debugging("Request for $uri failed with curl error $curlerrno");
108     } 
110     // check HTTP error code
111     $info =  curl_getinfo($ch);
112     if (!empty($info['http_code']) and ($info['http_code'] != 200)) {
113         debugging("Request for $uri failed with HTTP code ".$info['http_code']);
114     }
116     curl_close($ch);
118     if (!is_array($res)) { // ! error
119         $public_certificate = $res;
120         $credentials=array();
121         if (strlen(trim($public_certificate))) {
122             $credentials = openssl_x509_parse($public_certificate);
123             $host = $credentials['subject']['CN'];
124             if (array_key_exists( 'subjectAltName', $credentials['subject'])) {
125                 $host = $credentials['subject']['subjectAltName'];
126             }
127             if (strpos($uri, $host) !== false) {
128                 mnet_set_public_key($uri, $public_certificate);
129                 return $public_certificate;
130             }
131             else {
132                 debugging("Request for $uri returned public key for different URI - $host");
133             }
134         }
135         else {
136             debugging("Request for $uri returned empty response");
137         }
138     }
139     else {
140         debugging( "Request for $uri returned unexpected result");
141     }
142     return false;
145 /**
146  * Store a URI's public key in a static variable, or retrieve the key for a URI
147  *
148  * @param  string  $uri  The URI of a file on the remote computer, including its
149  *                       https:// prefix
150  * @param  mixed   $key  A public key to store in the array OR null. If the key
151  *                       is null, the function will return the previously stored
152  *                       key for the supplied URI, should it exist.
153  * @return mixed         A public key OR true/false.
154  */
155 function mnet_set_public_key($uri, $key = null) {
156     static $keyarray = array();
157     if (isset($keyarray[$uri]) && empty($key)) {
158         return $keyarray[$uri];
159     } elseif (!empty($key)) {
160         $keyarray[$uri] = $key;
161         return true;
162     }
163     return false;
166 /**
167  * Sign a message and return it in an XML-Signature document
168  *
169  * This function can sign any content, but it was written to provide a system of
170  * signing XML-RPC request and response messages. The message will be base64
171  * encoded, so it does not need to be text.
172  *
173  * We compute the SHA1 digest of the message.
174  * We compute a signature on that digest with our private key.
175  * We link to the public key that can be used to verify our signature.
176  * We base64 the message data.
177  * We identify our wwwroot - this must match our certificate's CN
178  *
179  * The XML-RPC document will be parceled inside an XML-SIG document, which holds
180  * the base64_encoded XML as an object, the SHA1 digest of that document, and a
181  * signature of that document using the local private key. This signature will
182  * uniquely identify the RPC document as having come from this server.
183  *
184  * See the {@Link http://www.w3.org/TR/xmldsig-core/ XML-DSig spec} at the W3c
185  * site
186  *
187  * @param  string   $message              The data you want to sign
188  * @param  resource $privatekey           The private key to sign the response with
189  * @return string                         An XML-DSig document
190  */
191 function mnet_sign_message($message, $privatekey = null) {
192     global $CFG, $MNET;
193     $digest = sha1($message);
195     // If the user hasn't supplied a private key (for example, one of our older,
196     //  expired private keys, we get the current default private key and use that.
197     if ($privatekey == null) {
198         $privatekey = $MNET->get_private_key();
199     }
201     // The '$sig' value below is returned by reference.
202     // We initialize it first to stop my IDE from complaining.
203     $sig  = '';
204     $bool = openssl_sign($message, $sig, $privatekey); // TODO: On failure?
206     $message = '<?xml version="1.0" encoding="iso-8859-1"?>
207     <signedMessage>
208         <Signature Id="MoodleSignature" xmlns="http://www.w3.org/2000/09/xmldsig#">
209             <SignedInfo>
210                 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
211                 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/>
212                 <Reference URI="#XMLRPC-MSG">
213                     <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
214                     <DigestValue>'.$digest.'</DigestValue>
215                 </Reference>
216             </SignedInfo>
217             <SignatureValue>'.base64_encode($sig).'</SignatureValue>
218             <KeyInfo>
219                 <RetrievalMethod URI="'.$CFG->wwwroot.'/mnet/publickey.php"/>
220             </KeyInfo>
221         </Signature>
222         <object ID="XMLRPC-MSG">'.base64_encode($message).'</object>
223         <wwwroot>'.$MNET->wwwroot.'</wwwroot>
224         <timestamp>'.time().'</timestamp>
225     </signedMessage>';
226     return $message;
229 /**
230  * Encrypt a message and return it in an XML-Encrypted document
231  *
232  * This function can encrypt any content, but it was written to provide a system
233  * of encrypting XML-RPC request and response messages. The message will be
234  * base64 encoded, so it does not need to be text - binary data should work.
235  *
236  * We compute the SHA1 digest of the message.
237  * We compute a signature on that digest with our private key.
238  * We link to the public key that can be used to verify our signature.
239  * We base64 the message data.
240  * We identify our wwwroot - this must match our certificate's CN
241  *
242  * The XML-RPC document will be parceled inside an XML-SIG document, which holds
243  * the base64_encoded XML as an object, the SHA1 digest of that document, and a
244  * signature of that document using the local private key. This signature will
245  * uniquely identify the RPC document as having come from this server.
246  *
247  * See the {@Link http://www.w3.org/TR/xmlenc-core/ XML-ENC spec} at the W3c
248  * site
249  *
250  * @param  string   $message              The data you want to sign
251  * @param  string   $remote_certificate   Peer's certificate in PEM format
252  * @return string                         An XML-ENC document
253  */
254 function mnet_encrypt_message($message, $remote_certificate) {
255     global $MNET;
257     // Generate a key resource from the remote_certificate text string
258     $publickey = openssl_get_publickey($remote_certificate);
260     if ( gettype($publickey) != 'resource' ) {
261         // Remote certificate is faulty.
262         return false;
263     }
265     // Initialize vars
266     $encryptedstring = '';
267     $symmetric_keys = array();
269     //        passed by ref ->     &$encryptedstring &$symmetric_keys
270     $bool = openssl_seal($message, $encryptedstring, $symmetric_keys, array($publickey));
271     $message = $encryptedstring;
272     $symmetrickey = array_pop($symmetric_keys);
274     $message = '<?xml version="1.0" encoding="iso-8859-1"?>
275     <encryptedMessage>
276         <EncryptedData Id="ED" xmlns="http://www.w3.org/2001/04/xmlenc#">
277             <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#arcfour"/>
278             <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
279                 <ds:RetrievalMethod URI="#EK" Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/>
280                 <ds:KeyName>XMLENC</ds:KeyName>
281             </ds:KeyInfo>
282             <CipherData>
283                 <CipherValue>'.base64_encode($message).'</CipherValue>
284             </CipherData>
285         </EncryptedData>
286         <EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#">
287             <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
288             <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
289                 <ds:KeyName>SSLKEY</ds:KeyName>
290             </ds:KeyInfo>
291             <CipherData>
292                 <CipherValue>'.base64_encode($symmetrickey).'</CipherValue>
293             </CipherData>
294             <ReferenceList>
295                 <DataReference URI="#ED"/>
296             </ReferenceList>
297             <CarriedKeyName>XMLENC</CarriedKeyName>
298         </EncryptedKey>
299         <wwwroot>'.$MNET->wwwroot.'</wwwroot>
300     </encryptedMessage>';
301     return $message;
304 /**
305  * Get your SSL keys from the database, or create them (if they don't exist yet)
306  *
307  * Get your SSL keys from the database, or (if they don't exist yet) call
308  * mnet_generate_keypair to create them
309  *
310  * @param   string  $string     The text you want to sign
311  * @return  string              The signature over that text
312  */
313 function mnet_get_keypair() {
314     global $CFG, $DB;;
315     static $keypair = null;
316     if (!is_null($keypair)) return $keypair;
317     if ($result = $DB->get_field('config_plugins', 'value', array('plugin'=>'mnet', 'name'=>'openssl'))) {
318         list($keypair['certificate'], $keypair['keypair_PEM']) = explode('@@@@@@@@', $result);
319         $keypair['privatekey'] = openssl_pkey_get_private($keypair['keypair_PEM']);
320         $keypair['publickey']  = openssl_pkey_get_public($keypair['certificate']);
321         return $keypair;
322     } else {
323         $keypair = mnet_generate_keypair();
324         return $keypair;
325     }
328 /**
329  * Generate public/private keys and store in the config table
330  *
331  * Use the distinguished name provided to create a CSR, and then sign that CSR
332  * with the same credentials. Store the keypair you create in the config table.
333  * If a distinguished name is not provided, create one using the fullname of
334  * 'the course with ID 1' as your organization name, and your hostname (as
335  * detailed in $CFG->wwwroot).
336  *
337  * @param   array  $dn  The distinguished name of the server
338  * @return  string      The signature over that text
339  */
340 function mnet_generate_keypair($dn = null, $days=28) {
341     global $CFG, $USER, $DB;
343     // check if lifetime has been overriden
344     if (!empty($CFG->mnetkeylifetime)) {
345         $days = $CFG->mnetkeylifetime;
346     }
348     $host = strtolower($CFG->wwwroot);
349     $host = ereg_replace("^http(s)?://",'',$host);
350     $break = strpos($host.'/' , '/');
351     $host   = substr($host, 0, $break);
353     if ($result = $DB->get_record('course', array("id"=>SITEID))) {
354         $organization = $result->fullname;
355     } else {
356         $organization = 'None';
357     }
359     $keypair = array();
361     $country  = 'NZ';
362     $province = 'Wellington';
363     $locality = 'Wellington';
364     $email    = $CFG->noreplyaddress;
366     if(!empty($USER->country)) {
367         $country  = $USER->country;
368     }
369     if(!empty($USER->city)) {
370         $province = $USER->city;
371         $locality = $USER->city;
372     }
373     if(!empty($USER->email)) {
374         $email    = $USER->email;
375     }
377     if (is_null($dn)) {
378         $dn = array(
379            "countryName" => $country,
380            "stateOrProvinceName" => $province,
381            "localityName" => $locality,
382            "organizationName" => $organization,
383            "organizationalUnitName" => 'Moodle',
384            "commonName" => substr($CFG->wwwroot, 0, 64),
385            "subjectAltName" => $CFG->wwwroot,
386            "emailAddress" => $email
387         );
388     }
390     // ensure we remove trailing slashes
391     $dn["commonName"] = preg_replace(':/$:', '', $dn["commonName"]);
393     $new_key = openssl_pkey_new();
394     $csr_rsc = openssl_csr_new($dn, $new_key, array('private_key_bits',2048));
395     $selfSignedCert = openssl_csr_sign($csr_rsc, null, $new_key, $days);
396     unset($csr_rsc); // Free up the resource
398     // We export our self-signed certificate to a string.
399     openssl_x509_export($selfSignedCert, $keypair['certificate']);
400     openssl_x509_free($selfSignedCert);
402     // Export your public/private key pair as a PEM encoded string. You
403     // can protect it with an optional passphrase if you wish.
404     $export = openssl_pkey_export($new_key, $keypair['keypair_PEM'] /* , $passphrase */);
405     openssl_pkey_free($new_key);
406     unset($new_key); // Free up the resource
408     return $keypair;
411 /**
412  * Check that an IP address falls within the given network/mask
413  * ok for export
414  *
415  * @param  string   $address        Dotted quad
416  * @param  string   $network        Dotted quad
417  * @param  string   $mask           A number, e.g. 16, 24, 32
418  * @return bool
419  */
420 function ip_in_range($address, $network, $mask) {
421    $lnetwork  = ip2long($network);
422    $laddress  = ip2long($address);
424    $binnet    = str_pad( decbin($lnetwork),32,"0","STR_PAD_LEFT" );
425    $firstpart = substr($binnet,0,$mask);
427    $binip     = str_pad( decbin($laddress),32,"0","STR_PAD_LEFT" );
428    $firstip   = substr($binip,0,$mask);
429    return(strcmp($firstpart,$firstip)==0);
432 /**
433  * Check that a given function (or method) in an include file has been designated
434  * ok for export
435  *
436  * @param  string   $includefile    The path to the include file
437  * @param  string   $functionname   The name of the function (or method) to
438  *                                  execute
439  * @param  mixed    $class          A class name, or false if we're just testing
440  *                                  a function
441  * @return int                      Zero (RPC_OK) if all ok - appropriate
442  *                                  constant otherwise
443  */
444 function mnet_permit_rpc_call($includefile, $functionname, $class=false) {
445     global $CFG, $MNET_REMOTE_CLIENT, $DB;
447     if (file_exists($CFG->dirroot . $includefile)) {
448         include_once $CFG->dirroot . $includefile;
449         // $callprefix matches the rpc convention
450         // of not having a leading slash
451         $callprefix = preg_replace('!^/!', '', $includefile);
452     } else {
453         return RPC_NOSUCHFILE;
454     }
456     if ($functionname != clean_param($functionname, PARAM_PATH)) {
457         // Under attack?
458         // Todo: Should really return a much more BROKEN! response
459         return RPC_FORBIDDENMETHOD;
460     }
462     $id_list = $MNET_REMOTE_CLIENT->id;
463     if (!empty($CFG->mnet_all_hosts_id)) {
464         $id_list .= ', '.$CFG->mnet_all_hosts_id;
465     }
467     // TODO: change to left-join so we can disambiguate:
468     // 1. method doesn't exist
469     // 2. method exists but is prohibited
470     $sql = "
471         SELECT
472             count(r.id)
473         FROM
474             {mnet_host2service} h2s,
475             {mnet_service2rpc} s2r,
476             {mnet_rpc} r
477         WHERE
478             h2s.serviceid = s2r.serviceid AND
479             s2r.rpcid = r.id AND
480             r.xmlrpc_path = ? AND
481             h2s.hostid in ($id_list) AND
482             h2s.publish = '1'";
483     $params = array("$callprefix/$functionname");
484     $permissionobj = $DB->record_exists_sql($sql, $params);
486     if ($permissionobj === false && 'dangerous' != $CFG->mnet_dispatcher_mode) {
487         return RPC_FORBIDDENMETHOD;
488     }
490     // WE'RE LOOKING AT A CLASS/METHOD
491     if (false != $class) {
492         if (!class_exists($class)) {
493             // Generate error response - unable to locate class
494             return RPC_NOSUCHCLASS;
495         }
497         $object = false;
498         $static = false;
500         try {
501             // @todo set here whatever we can to ensure that errors cause exceptions :(
502             // see MDL-16175 - long term solution is teaching the dispatcher about:
503             // a: the difference between static and class methods
504             // b: object constructor arguments
505             $object = @new $class();
506             if (!method_exists($object, $functionname)) {
507                 // Generate error response - unable to locate method
508                 return RPC_NOSUCHMETHOD;
509             }
511             if (!method_exists($object, 'mnet_publishes')) {
512                 // Generate error response - the class doesn't publish
513                 // *any* methods, because it doesn't have an mnet_publishes
514                 // method
515                 return RPC_FORBIDDENMETHOD;
516             }
518             // Get the list of published services - initialise method array
519             $servicelist = $object->mnet_publishes();
520         }
521         catch (Exception $e) {
522             if (method_exists($class, $functionname)) {
523                 // static... don't try to instantiate it.
524                 if (!method_exists($class, 'mnet_publishes')) {
525                     return RPC_FORBIDDENMETHOD;
526                 } else {
527                     $servicelist = call_user_func(array($class, 'mnet_publishes'));
528                 }
529                 $static = $class;
530             }
531         }
533         $methodapproved = false;
535         // If the method is in the list of approved methods, set the
536         // methodapproved flag to true and break
537         foreach($servicelist as $service) {
538             if (in_array($functionname, $service['methods'])) {
539                 $methodapproved = true;
540                 break;
541             }
542         }
544         if (!$methodapproved) {
545             return RPC_FORBIDDENMETHOD;
546         }
548         // Stash the object so we can call the method on it later
549         $MNET_REMOTE_CLIENT->object_to_call($object);
550         $MNET_REMOTE_CLIENT->static_location($static);
552     // WE'RE LOOKING AT A FUNCTION
553     } else {
554         if (!function_exists($functionname)) {
555             // Generate error response - unable to locate function
556             return RPC_NOSUCHFUNCTION;
557         }
559     }
561     return RPC_OK;
564 function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) {
565     global $DB;
567     $mnethost = $DB->get_record('mnet_host', array('id'=>$mnet_host_id));
568     if ($aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnet_host_id))) {
569         // update
570         $aclrecord->accessctrl = $accessctrl;
571         if ($DB->update_record('mnet_sso_access_control', $aclrecord)) {
572             add_to_log(SITEID, 'admin/mnet', 'update', 'admin/mnet/access_control.php',
573                     "SSO ACL: $accessctrl user '$username' from {$mnethost->name}");
574         } else {
575             print_error('failedaclwrite', 'mnet', '', $username);
576             return false;
577         }
578     } else {
579         // insert
580         $aclrecord->username = $username;
581         $aclrecord->accessctrl = $accessctrl;
582         $aclrecord->mnet_host_id = $mnet_host_id;
583         if ($id = $DB->insert_record('mnet_sso_access_control', $aclrecord)) {
584             add_to_log(SITEID, 'admin/mnet', 'add', 'admin/mnet/access_control.php',
585                     "SSO ACL: $accessctrl user '$username' from {$mnethost->name}");
586         } else {
587             print_error('failedaclwrite', 'mnet', '', $username);
588             return false;
589         }
590     }
591     return true;
594 function mnet_get_peer_host ($mnethostid) {
595     global $DB;
596     static $hosts;
597     if (!isset($hosts[$mnethostid])) {
598         $host = $DB->get_record('mnet_host', array('id' => $mnethostid));
599         $hosts[$mnethostid] = $host;
600     }
601     return $hosts[$mnethostid];
604 /**
605  * Inline function to modify a url string so that mnet users are requested to
606  * log in at their mnet identity provider (if they are not already logged in)
607  * before ultimately being directed to the original url.
608  *
609  * uses global MNETIDPJUMPURL the url which user should initially be directed to
610  *     MNETIDPJUMPURL is a URL associated with a moodle networking peer when it
611  *     is fulfiling a role as an identity provider (IDP). Different urls for
612  *     different peers, the jumpurl is formed partly from the IDP's webroot, and
613  *     partly from a predefined local path within that webwroot.
614  *     The result of the user hitting MNETIDPJUMPURL is that they will be asked
615  *     to login (at their identity provider (if they aren't already)), mnet
616  *     will prepare the necessary authentication information, then redirect
617  *     them back to somewhere at the content provider(CP) moodle (this moodle)
618  * @param array $url array with 2 elements
619  *     0 - context the url was taken from, possibly just the url, possibly href="url"
620  *     1 - the destination url
621  * @return string the url the remote user should be supplied with.
622  */
623 function mnet_sso_apply_indirection ($url) {
624     global $MNETIDPJUMPURL;
626     $localpart='';
627     $urlparts = parse_url($url[1]);
628     if($urlparts) {
629         if (isset($urlparts['path'])) {
630             $localpart .= $urlparts['path'];
631         }
632         if (isset($urlparts['query'])) {
633             $localpart .= '?'.$urlparts['query'];
634         }
635         if (isset($urlparts['fragment'])) {
636             $localpart .= '#'.$urlparts['fragment'];
637         }
638     }
639     $indirecturl = $MNETIDPJUMPURL . urlencode($localpart);
640     //If we matched on more than just a url (ie an html link), return the url to an href format
641     if ($url[0] != $url[1]) {
642         $indirecturl = 'href="'.$indirecturl.'"';
643     }
644     return $indirecturl;
647 function mnet_get_app_jumppath ($applicationid) {
648     global $DB;
649     static $appjumppaths;
650     if (!isset($appjumppaths[$applicationid])) {
651         $ssojumpurl = $DB->get_field('mnet_application', 'sso_jump_url', array('id' => $applicationid));
652         $appjumppaths[$applicationid] = $ssojumpurl;
653     }
654     return $appjumppaths[$applicationid];
657 ?>