Beispiel #1
0
 function refresh_key()
 {
     mnet_debug("remote client refreshing key");
     global $CFG;
     // set up an RPC request
     require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
     $mnetrequest = new mnet_xmlrpc_client();
     // Use any method - listServices is pretty lightweight.
     $mnetrequest->set_method('system/listServices');
     // Do RPC call and store response
     if ($mnetrequest->send($this) === true) {
         mnet_debug("refresh key request complete");
         // Ok - we actually don't care about the result
         $temp = new mnet_peer();
         $temp->set_id($this->id);
         if ($this->public_key != $temp->public_key) {
             $newkey = clean_param($temp->public_key, PARAM_PEM);
             if (!empty($newkey)) {
                 $this->public_key = $newkey;
                 return true;
             }
         }
     }
     return false;
 }
 /**
  * This function confirms the remote (ID provider) host's mnet session
  * by communicating the token and UA over the XMLRPC transport layer, and
  * returns the local user record on success.
  *
  *   @param string    $token           The random session token.
  *   @param mnet_peer $remotepeer   The ID provider mnet_peer object.
  *   @return array The local user record.
  */
 function confirm_mnet_session($token, $remotepeer)
 {
     global $CFG, $DB;
     require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
     require_once $CFG->libdir . '/gdlib.php';
     // verify the remote host is configured locally before attempting RPC call
     if (!($remotehost = $DB->get_record('mnet_host', array('wwwroot' => $remotepeer->wwwroot, 'deleted' => 0)))) {
         print_error('notpermittedtoland', 'mnet');
     }
     // set up the RPC request
     $mnetrequest = new mnet_xmlrpc_client();
     $mnetrequest->set_method('auth/mnet/auth.php/user_authorise');
     // set $token and $useragent parameters
     $mnetrequest->add_param($token);
     $mnetrequest->add_param(sha1($_SERVER['HTTP_USER_AGENT']));
     // Thunderbirds are go! Do RPC call and store response
     if ($mnetrequest->send($remotepeer) === true) {
         $remoteuser = (object) $mnetrequest->response;
     } else {
         foreach ($mnetrequest->error as $errormessage) {
             list($code, $message) = array_map('trim', explode(':', $errormessage, 2));
             if ($code == 702) {
                 $site = get_site();
                 print_error('mnet_session_prohibited', 'mnet', $remotepeer->wwwroot, format_string($site->fullname));
                 exit;
             }
             $message .= "ERROR {$code}:<br/>{$errormessage}<br/>";
         }
         print_error("rpcerror", '', '', $message);
     }
     unset($mnetrequest);
     if (empty($remoteuser) or empty($remoteuser->username)) {
         print_error('unknownerror', 'mnet');
         exit;
     }
     if (user_not_fully_set_up($remoteuser)) {
         print_error('notenoughidpinfo', 'mnet');
         exit;
     }
     $remoteuser = mnet_strip_user($remoteuser, mnet_fields_to_import($remotepeer));
     $remoteuser->auth = 'mnet';
     $remoteuser->wwwroot = $remotepeer->wwwroot;
     // the user may roam from Moodle 1.x where lang has _utf8 suffix
     // also, make sure that the lang is actually installed, otherwise set site default
     if (isset($remoteuser->lang)) {
         $remoteuser->lang = clean_param(str_replace('_utf8', '', $remoteuser->lang), PARAM_LANG);
     }
     if (empty($remoteuser->lang)) {
         if (!empty($CFG->lang)) {
             $remoteuser->lang = $CFG->lang;
         } else {
             $remoteuser->lang = 'en';
         }
     }
     $firsttime = false;
     // get the local record for the remote user
     $localuser = $DB->get_record('user', array('username' => $remoteuser->username, 'mnethostid' => $remotehost->id));
     // add the remote user to the database if necessary, and if allowed
     // TODO: refactor into a separate function
     if (empty($localuser) || !$localuser->id) {
         /*
         if (empty($this->config->auto_add_remote_users)) {
             print_error('nolocaluser', 'mnet');
         } See MDL-21327   for why this is commented out
         */
         $remoteuser->mnethostid = $remotehost->id;
         $remoteuser->firstaccess = time();
         // First time user in this server, grab it here
         $remoteuser->id = $DB->insert_record('user', $remoteuser);
         $firsttime = true;
         $localuser = $remoteuser;
     }
     // check sso access control list for permission first
     if (!$this->can_login_remotely($localuser->username, $remotehost->id)) {
         print_error('sso_mnet_login_refused', 'mnet', '', array('user' => $localuser->username, 'host' => $remotehost->name));
     }
     $fs = get_file_storage();
     // update the local user record with remote user data
     foreach ((array) $remoteuser as $key => $val) {
         if ($key == '_mnet_userpicture_timemodified' and empty($CFG->disableuserimages) and isset($remoteuser->picture)) {
             // update the user picture if there is a newer verion at the identity provider
             $usercontext = get_context_instance(CONTEXT_USER, $localuser->id, MUST_EXIST);
             if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.png')) {
                 $localtimemodified = $usericonfile->get_timemodified();
             } else {
                 if ($usericonfile = $fs->get_file($usercontext->id, 'user', 'icon', 0, '/', 'f1.jpg')) {
                     $localtimemodified = $usericonfile->get_timemodified();
                 } else {
                     $localtimemodified = 0;
                 }
             }
             if (!empty($val) and $localtimemodified < $val) {
                 mnet_debug('refetching the user picture from the identity provider host');
                 $fetchrequest = new mnet_xmlrpc_client();
                 $fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image');
                 $fetchrequest->add_param($localuser->username);
                 if ($fetchrequest->send($remotepeer) === true) {
                     if (strlen($fetchrequest->response['f1']) > 0) {
                         $imagefilename = $CFG->dataroot . '/temp/mnet-usericon-' . $localuser->id;
                         $imagecontents = base64_decode($fetchrequest->response['f1']);
                         file_put_contents($imagefilename, $imagecontents);
                         if (process_new_icon($usercontext, 'user', 'icon', 0, $imagefilename)) {
                             $localuser->picture = 1;
                         }
                         unlink($imagefilename);
                     }
                     // note that since Moodle 2.0 we ignore $fetchrequest->response['f2']
                     // the mimetype information provided is ignored and the type of the file is detected
                     // by process_new_icon()
                 }
             }
         }
         if ($key == 'myhosts') {
             $localuser->mnet_foreign_host_array = array();
             foreach ($val as $rhost) {
                 $name = clean_param($rhost['name'], PARAM_ALPHANUM);
                 $url = clean_param($rhost['url'], PARAM_URL);
                 $count = clean_param($rhost['count'], PARAM_INT);
                 $url_is_local = stristr($url, $CFG->wwwroot);
                 if (!empty($name) && !empty($count) && empty($url_is_local)) {
                     $localuser->mnet_foreign_host_array[] = array('name' => $name, 'url' => $url, 'count' => $count);
                 }
             }
         }
         $localuser->{$key} = $val;
     }
     $localuser->mnethostid = $remotepeer->id;
     if (empty($localuser->firstaccess)) {
         // Now firstaccess, grab it here
         $localuser->firstaccess = time();
     }
     $DB->update_record('user', $localuser);
     if (!$firsttime) {
         // repeat customer! let the IDP know about enrolments
         // we have for this user.
         // set up the RPC request
         $mnetrequest = new mnet_xmlrpc_client();
         $mnetrequest->set_method('auth/mnet/auth.php/update_enrolments');
         // pass username and an assoc array of "my courses"
         // with info so that the IDP can maintain mnetservice_enrol_enrolments
         $mnetrequest->add_param($remoteuser->username);
         $fields = 'id, category, sortorder, fullname, shortname, idnumber, summary, startdate, visible';
         $courses = enrol_get_users_courses($localuser->id, false, $fields, 'visible DESC,sortorder ASC');
         if (is_array($courses) && !empty($courses)) {
             // Second request to do the JOINs that we'd have done
             // inside enrol_get_users_courses() if we had been allowed
             $sql = "SELECT c.id,\n                               cc.name AS cat_name, cc.description AS cat_description\n                          FROM {course} c\n                          JOIN {course_categories} cc ON c.category = cc.id\n                         WHERE c.id IN (" . join(',', array_keys($courses)) . ')';
             $extra = $DB->get_records_sql($sql);
             $keys = array_keys($courses);
             $defaultrole = reset(get_archetype_roles('student'));
             //$defaultrole = get_default_course_role($ccache[$shortname]); //TODO: rewrite this completely, there is no default course role any more!!!
             foreach ($keys as $id) {
                 if ($courses[$id]->visible == 0) {
                     unset($courses[$id]);
                     continue;
                 }
                 $courses[$id]->cat_id = $courses[$id]->category;
                 $courses[$id]->defaultroleid = $defaultrole->id;
                 unset($courses[$id]->category);
                 unset($courses[$id]->visible);
                 $courses[$id]->cat_name = $extra[$id]->cat_name;
                 $courses[$id]->cat_description = $extra[$id]->cat_description;
                 $courses[$id]->defaultrolename = $defaultrole->name;
                 // coerce to array
                 $courses[$id] = (array) $courses[$id];
             }
         } else {
             // if the array is empty, send it anyway
             // we may be clearing out stale entries
             $courses = array();
         }
         $mnetrequest->add_param($courses);
         // Call 0800-RPC Now! -- we don't care too much if it fails
         // as it's just informational.
         if ($mnetrequest->send($remotepeer) === false) {
             // error_log(print_r($mnetrequest->error,1));
         }
     }
     return $localuser;
 }
Beispiel #3
0
/**
 * Return the proper XML-RPC content to report an error.
 *
 * @param  int      $code   The ID code of the error message
 * @param  string   $text   The error message
 * @param  resource $privatekey The private key that should be used to sign the response
 * @return string   $text   The XML text of the error message
 */
function mnet_server_fault_xml($code, $text, $privatekey = null) {
    global $CFG;
    // Replace illegal XML chars - is this already in a lib somewhere?
    $text = str_replace(array('<','>','&','"',"'"), array('&lt;','&gt;','&amp;','&quot;','&apos;'), $text);

    $return = mnet_server_prepare_response('<?xml version="1.0"?>
<methodResponse>
   <fault>
      <value>
         <struct>
            <member>
               <name>faultCode</name>
               <value><int>'.$code.'</int></value>
            </member>
            <member>
               <name>faultString</name>
               <value><string>'.$text.'</string></value>
            </member>
         </struct>
      </value>
   </fault>
</methodResponse>', $privatekey);

    if ($code != 7025) { // new key responses
        mnet_debug("XMLRPC Error Response $code: $text");
        //mnet_debug($return);
    }

    return $return;
}
Beispiel #4
0
/**
 * Output debug information about mnet.  this will go to the <b>error_log</b>.
 *
 * @param mixed $debugdata this can be a string, or array or object.
 * @param int   $debuglevel optional , defaults to 1. bump up for very noisy debug info
 */
function mnet_debug($debugdata, $debuglevel=1) {
    global $CFG;
    $setlevel = get_config('', 'mnet_rpcdebug');
    if (empty($setlevel) || $setlevel < $debuglevel) {
        return;
    }
    if (is_object($debugdata)) {
        $debugdata = (array)$debugdata;
    }
    if (is_array($debugdata)) {
        mnet_debug('DUMPING ARRAY');
        foreach ($debugdata as $key => $value) {
            mnet_debug("$key: $value");
        }
        mnet_debug('END DUMPING ARRAY');
        return;
    }
    $prefix = 'MNET DEBUG ';
    if (defined('MNET_SERVER')) {
        $prefix .= " (server $CFG->wwwroot";
        if ($peer = get_mnet_remote_client() && !empty($peer->wwwroot)) {
            $prefix .= ", remote peer " . $peer->wwwroot;
        }
        $prefix .= ')';
    } else {
        $prefix .= " (client $CFG->wwwroot) ";
    }
    error_log("$prefix $debugdata");
}
Beispiel #5
0
// 2. Request is for a keyswap (we don't mind enencrypted or unsigned requests for a public key)
// 3. Request is properly signed and we're happy with it being unencrypted
if ($remoteclient->request_was_encrypted == true && $remoteclient->signatureok == true || ($method == 'system.keyswap' || $method == 'system/keyswap') || $remoteclient->signatureok == true && $remoteclient->plaintext_is_ok() == true) {
    try {
        // main dispatch call.  will echo the response directly
        mnet_server_dispatch($xmlrpcrequest);
        mnet_debug('exiting cleanly');
        exit;
    } catch (Exception $e) {
        mnet_debug('dispatch exception thrown: ' . $e->getMessage());
        exit(mnet_server_fault($e->getCode(), $e->getMessage(), $e->a));
    }
}
// if we get to here, something is wrong
// so detect a few common cases and send appropriate errors
if ($remoteclient->request_was_encrypted == false && $remoteclient->plaintext_is_ok() == false) {
    mnet_debug('non encrypted request');
    exit(mnet_server_fault(7021, get_string('forbidden-transport', 'mnet')));
}
if ($remoteclient->request_was_signed == false) {
    // Request was not signed
    mnet_debug('non signed request');
    exit(mnet_server_fault(711, get_string('verifysignature-error', 'mnet')));
}
if ($remoteclient->signatureok == false) {
    // We were unable to verify the signature
    mnet_debug('non verified signature');
    exit(mnet_server_fault(710, get_string('verifysignature-invalid', 'mnet')));
}
mnet_debug('unknown error');
exit(mnet_server_fault(7000, get_string('unknownerror', 'mnet')));
Beispiel #6
0
 /**
  * Send the request to the server - decode and return the response
  *
  * @param  object   $mnet_peer      A mnet_peer object with details of the
  *                                  remote host we're connecting to
  * @return mixed                    A PHP variable, as returned by the
  *                                  remote function
  */
 function send($mnet_peer)
 {
     global $CFG, $DB;
     if (!$this->permission_to_call($mnet_peer)) {
         mnet_debug("tried and wasn't allowed to call a method on {$mnet_peer->wwwroot}");
         return false;
     }
     $this->requesttext = xmlrpc_encode_request($this->method, $this->params, array("encoding" => "utf-8", "escaping" => "markup"));
     $this->signedrequest = mnet_sign_message($this->requesttext);
     $this->encryptedrequest = mnet_encrypt_message($this->signedrequest, $mnet_peer->public_key);
     $httprequest = $this->prepare_http_request($mnet_peer);
     curl_setopt($httprequest, CURLOPT_POSTFIELDS, $this->encryptedrequest);
     $timestamp_send = time();
     mnet_debug("about to send the curl request");
     $this->rawresponse = curl_exec($httprequest);
     mnet_debug("managed to complete a curl request");
     $timestamp_receive = time();
     if ($this->rawresponse === false) {
         $this->error[] = curl_errno($httprequest) . ':' . curl_error($httprequest);
         return false;
     }
     curl_close($httprequest);
     $this->rawresponse = trim($this->rawresponse);
     $mnet_peer->touch();
     $crypt_parser = new mnet_encxml_parser();
     $crypt_parser->parse($this->rawresponse);
     // If we couldn't parse the message, or it doesn't seem to have encrypted contents,
     // give the most specific error msg available & return
     if (!$crypt_parser->payload_encrypted) {
         if (!empty($crypt_parser->remoteerror)) {
             $this->error[] = '4: remote server error: ' . $crypt_parser->remoteerror;
         } else {
             if (!empty($crypt_parser->error)) {
                 $crypt_parser_error = $crypt_parser->error[0];
                 $message = '3:XML Parse error in payload: ' . $crypt_parser_error['string'] . "\n";
                 if (array_key_exists('lineno', $crypt_parser_error)) {
                     $message .= 'At line number: ' . $crypt_parser_error['lineno'] . "\n";
                 }
                 if (array_key_exists('line', $crypt_parser_error)) {
                     $message .= 'Which reads: ' . $crypt_parser_error['line'] . "\n";
                 }
                 $this->error[] = $message;
             } else {
                 $this->error[] = '1:Payload not encrypted ';
             }
         }
         $crypt_parser->free_resource();
         return false;
     }
     $key = array_pop($crypt_parser->cipher);
     $data = array_pop($crypt_parser->cipher);
     $crypt_parser->free_resource();
     // Initialize payload var
     $decryptedenvelope = '';
     //                                          &$decryptedenvelope
     $isOpen = openssl_open(base64_decode($data), $decryptedenvelope, base64_decode($key), $this->mnet->get_private_key());
     if (!$isOpen) {
         // Decryption failed... let's try our archived keys
         $openssl_history = get_config('mnet', 'openssl_history');
         if (empty($openssl_history)) {
             $openssl_history = array();
             set_config('openssl_history', serialize($openssl_history), 'mnet');
         } else {
             $openssl_history = unserialize($openssl_history);
         }
         foreach ($openssl_history as $keyset) {
             $keyresource = openssl_pkey_get_private($keyset['keypair_PEM']);
             $isOpen = openssl_open(base64_decode($data), $decryptedenvelope, base64_decode($key), $keyresource);
             if ($isOpen) {
                 // It's an older code, sir, but it checks out
                 break;
             }
         }
     }
     if (!$isOpen) {
         trigger_error("None of our keys could open the payload from host {$mnet_peer->wwwroot} with id {$mnet_peer->id}.");
         $this->error[] = '3:No key match';
         return false;
     }
     if (strpos(substr($decryptedenvelope, 0, 100), '<signedMessage>')) {
         $sig_parser = new mnet_encxml_parser();
         $sig_parser->parse($decryptedenvelope);
     } else {
         $this->error[] = '2:Payload not signed: ' . $decryptedenvelope;
         return false;
     }
     // Margin of error is the time it took the request to complete.
     $margin_of_error = $timestamp_receive - $timestamp_send;
     // Guess the time gap between sending the request and the remote machine
     // executing the time() function. Marginally better than nothing.
     $hysteresis = $margin_of_error / 2;
     $remote_timestamp = $sig_parser->remote_timestamp - $hysteresis;
     $time_offset = $remote_timestamp - $timestamp_send;
     if ($time_offset > 0) {
         $threshold = get_config('mnet', 'drift_threshold');
         if (empty($threshold)) {
             // We decided 15 seconds was a pretty good arbitrary threshold
             // for time-drift between servers, but you can customize this in
             // the config_plugins table. It's not advised though.
             set_config('drift_threshold', 15, 'mnet');
             $threshold = 15;
         }
         if ($time_offset > $threshold) {
             $this->error[] = '6:Time gap with ' . $mnet_peer->name . ' (' . $time_offset . ' seconds) is greater than the permitted maximum of ' . $threshold . ' seconds';
             return false;
         }
     }
     $this->xmlrpcresponse = base64_decode($sig_parser->data_object);
     $this->response = xmlrpc_decode($this->xmlrpcresponse);
     // xmlrpc errors are pushed onto the $this->error stack
     if (is_array($this->response) && array_key_exists('faultCode', $this->response)) {
         // The faultCode 7025 means we tried to connect with an old SSL key
         // The faultString is the new key - let's save it and try again
         // The re_key attribute stops us from getting into a loop
         if ($this->response['faultCode'] == 7025 && empty($mnet_peer->re_key)) {
             mnet_debug('recieved an old-key fault, so trying to get the new key and update our records');
             // If the new certificate doesn't come thru clean_param() unmolested, error out
             if ($this->response['faultString'] != clean_param($this->response['faultString'], PARAM_PEM)) {
                 $this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'];
             }
             $record = new stdClass();
             $record->id = $mnet_peer->id;
             $record->public_key = $this->response['faultString'];
             $details = openssl_x509_parse($record->public_key);
             if (!isset($details['validTo_time_t'])) {
                 $this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'];
             }
             $record->public_key_expires = $details['validTo_time_t'];
             $DB->update_record('mnet_host', $record);
             // Create a new peer object populated with the new info & try re-sending the request
             $rekeyed_mnet_peer = new mnet_peer();
             $rekeyed_mnet_peer->set_id($record->id);
             $rekeyed_mnet_peer->re_key = true;
             return $this->send($rekeyed_mnet_peer);
         }
         if (!empty($CFG->mnet_rpcdebug)) {
             if (get_string_manager()->string_exists('error' . $this->response['faultCode'], 'mnet')) {
                 $guidance = get_string('error' . $this->response['faultCode'], 'mnet');
             } else {
                 $guidance = '';
             }
         } else {
             $guidance = '';
         }
         $this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'] . "\n" . $guidance;
     }
     // ok, it's signed, but is it signed with the right certificate ?
     // do this *after* we check for an out of date key
     if (!openssl_verify($this->xmlrpcresponse, base64_decode($sig_parser->signature), $mnet_peer->public_key)) {
         $this->error[] = 'Invalid signature';
     }
     return empty($this->error);
 }