Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • /mnet/ -> lib.php (source)

    Differences Between: [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       1  <?php
       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';
      13  
      14  /// CONSTANTS ///////////////////////////////////////////////////////////
      15  
      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);
      23  
      24  /**
      25   * Strip extraneous detail from a URL or URI and return the hostname
      26   *
      27   * @param  string  $uri  The URI of a file on the remote computer, optionally
      28   *                       including its http:// prefix like
      29   *                       http://www.example.com/index.html
      30   * @return string        Just the hostname
      31   */
      32  function mnet_get_hostname_from_uri($uri = null) {
      33      $count = preg_match("@^(?:http[s]?://)?([A-Z0-9\-\.]+).*@i", $uri, $matches);
      34      if ($count > 0) return $matches[1];
      35      return false;
      36  }
      37  
      38  /**
      39   * Get the remote machine's SSL Cert
      40   *
      41   * @param  string  $uri     The URI of a file on the remote computer, including
      42   *                          its http:// or https:// prefix
      43   * @return string           A PEM formatted SSL Certificate.
      44   */
      45  function mnet_get_public_key($uri, $application=null) {
      46      global $CFG, $DB;
      47      $mnet = get_mnet_environment();
      48      // The key may be cached in the mnet_set_public_key function...
      49      // check this first
      50      $key = mnet_set_public_key($uri);
      51      if ($key != false) {
      52          return $key;
      53      }
      54  
      55      if (empty($application)) {
      56          $application = $DB->get_record('mnet_application', array('name'=>'moodle'));
      57      }
      58  
      59      $rq = xmlrpc_encode_request('system/keyswap', array($CFG->wwwroot, $mnet->public_key, $application->name), array("encoding" => "utf-8"));
      60      $ch = curl_init($uri . $application->xmlrpc_server_url);
      61  
      62      curl_setopt($ch, CURLOPT_TIMEOUT, 60);
      63      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      64      curl_setopt($ch, CURLOPT_POST, true);
      65      curl_setopt($ch, CURLOPT_USERAGENT, 'Moodle');
      66      curl_setopt($ch, CURLOPT_POSTFIELDS, $rq);
      67      curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml charset=UTF-8"));
      68      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
      69      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
      70  
      71      // check for proxy
      72      if (!empty($CFG->proxyhost) and !is_proxybypass($uri)) {
      73          // SOCKS supported in PHP5 only
      74          if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
      75              if (defined('CURLPROXY_SOCKS5')) {
      76                  curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
      77              } else {
      78                  curl_close($ch);
      79                  print_error( 'socksnotsupported','mnet' );
      80              }
      81          }
      82  
      83          curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
      84  
      85          if (empty($CFG->proxyport)) {
      86              curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
      87          } else {
      88              curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
      89          }
      90  
      91          if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
      92              curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
      93              if (defined('CURLOPT_PROXYAUTH')) {
      94                  // any proxy authentication if PHP 5.1
      95                  curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
      96              }
      97          }
      98      }
      99  
     100      $res = xmlrpc_decode(curl_exec($ch));
     101  
     102      // check for curl errors
     103      $curlerrno = curl_errno($ch);
     104      if ($curlerrno!=0) {
     105          debugging("Request for $uri failed with curl error $curlerrno");
     106      }
     107  
     108      // check HTTP error code
     109      $info =  curl_getinfo($ch);
     110      if (!empty($info['http_code']) and ($info['http_code'] != 200)) {
     111          debugging("Request for $uri failed with HTTP code ".$info['http_code']);
     112      }
     113  
     114      curl_close($ch);
     115  
     116      if (!is_array($res)) { // ! error
     117          $public_certificate = $res;
     118          $credentials=array();
     119          if (strlen(trim($public_certificate))) {
     120              $credentials = openssl_x509_parse($public_certificate);
     121              $host = $credentials['subject']['CN'];
     122              if (array_key_exists( 'subjectAltName', $credentials['subject'])) {
     123                  $host = $credentials['subject']['subjectAltName'];
     124              }
     125              if (strpos($uri, $host) !== false) {
     126                  mnet_set_public_key($uri, $public_certificate);
     127                  return $public_certificate;
     128              }
     129              else {
     130                  debugging("Request for $uri returned public key for different URI - $host");
     131              }
     132          }
     133          else {
     134              debugging("Request for $uri returned empty response");
     135          }
     136      }
     137      else {
     138          debugging( "Request for $uri returned unexpected result");
     139      }
     140      return false;
     141  }
     142  
     143  /**
     144   * Store a URI's public key in a static variable, or retrieve the key for a URI
     145   *
     146   * @param  string  $uri  The URI of a file on the remote computer, including its
     147   *                       https:// prefix
     148   * @param  mixed   $key  A public key to store in the array OR null. If the key
     149   *                       is null, the function will return the previously stored
     150   *                       key for the supplied URI, should it exist.
     151   * @return mixed         A public key OR true/false.
     152   */
     153  function mnet_set_public_key($uri, $key = null) {
     154      static $keyarray = array();
     155      if (isset($keyarray[$uri]) && empty($key)) {
     156          return $keyarray[$uri];
     157      } elseif (!empty($key)) {
     158          $keyarray[$uri] = $key;
     159          return true;
     160      }
     161      return false;
     162  }
     163  
     164  /**
     165   * Sign a message and return it in an XML-Signature document
     166   *
     167   * This function can sign any content, but it was written to provide a system of
     168   * signing XML-RPC request and response messages. The message will be base64
     169   * encoded, so it does not need to be text.
     170   *
     171   * We compute the SHA1 digest of the message.
     172   * We compute a signature on that digest with our private key.
     173   * We link to the public key that can be used to verify our signature.
     174   * We base64 the message data.
     175   * We identify our wwwroot - this must match our certificate's CN
     176   *
     177   * The XML-RPC document will be parceled inside an XML-SIG document, which holds
     178   * the base64_encoded XML as an object, the SHA1 digest of that document, and a
     179   * signature of that document using the local private key. This signature will
     180   * uniquely identify the RPC document as having come from this server.
     181   *
     182   * See the {@Link http://www.w3.org/TR/xmldsig-core/ XML-DSig spec} at the W3c
     183   * site
     184   *
     185   * @param  string   $message              The data you want to sign
     186   * @param  resource $privatekey           The private key to sign the response with
     187   * @return string                         An XML-DSig document
     188   */
     189  function mnet_sign_message($message, $privatekey = null) {
     190      global $CFG;
     191      $digest = sha1($message);
     192  
     193      $mnet = get_mnet_environment();
     194      // If the user hasn't supplied a private key (for example, one of our older,
     195      //  expired private keys, we get the current default private key and use that.
     196      if ($privatekey == null) {
     197          $privatekey = $mnet->get_private_key();
     198      }
     199  
     200      // The '$sig' value below is returned by reference.
     201      // We initialize it first to stop my IDE from complaining.
     202      $sig  = '';
     203      $bool = openssl_sign($message, $sig, $privatekey); // TODO: On failure?
     204  
     205      $message = '<?xml version="1.0" encoding="iso-8859-1"?>
     206      <signedMessage>
     207          <Signature Id="MoodleSignature" xmlns="http://www.w3.org/2000/09/xmldsig#">
     208              <SignedInfo>
     209                  <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
     210                  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
     211                  <Reference URI="#XMLRPC-MSG">
     212                      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
     213                      <DigestValue>'.$digest.'</DigestValue>
     214                  </Reference>
     215              </SignedInfo>
     216              <SignatureValue>'.base64_encode($sig).'</SignatureValue>
     217              <KeyInfo>
     218                  <RetrievalMethod URI="'.$CFG->wwwroot.'/mnet/publickey.php"/>
     219              </KeyInfo>
     220          </Signature>
     221          <object ID="XMLRPC-MSG">'.base64_encode($message).'</object>
     222          <wwwroot>'.$mnet->wwwroot.'</wwwroot>
     223          <timestamp>'.time().'</timestamp>
     224      </signedMessage>';
     225      return $message;
     226  }
     227  
     228  /**
     229   * Encrypt a message and return it in an XML-Encrypted document
     230   *
     231   * This function can encrypt any content, but it was written to provide a system
     232   * of encrypting XML-RPC request and response messages. The message will be
     233   * base64 encoded, so it does not need to be text - binary data should work.
     234   *
     235   * We compute the SHA1 digest of the message.
     236   * We compute a signature on that digest with our private key.
     237   * We link to the public key that can be used to verify our signature.
     238   * We base64 the message data.
     239   * We identify our wwwroot - this must match our certificate's CN
     240   *
     241   * The XML-RPC document will be parceled inside an XML-SIG document, which holds
     242   * the base64_encoded XML as an object, the SHA1 digest of that document, and a
     243   * signature of that document using the local private key. This signature will
     244   * uniquely identify the RPC document as having come from this server.
     245   *
     246   * See the {@Link http://www.w3.org/TR/xmlenc-core/ XML-ENC spec} at the W3c
     247   * site
     248   *
     249   * @param  string   $message              The data you want to sign
     250   * @param  string   $remote_certificate   Peer's certificate in PEM format
     251   * @return string                         An XML-ENC document
     252   */
     253  function mnet_encrypt_message($message, $remote_certificate) {
     254      $mnet = get_mnet_environment();
     255  
     256      // Generate a key resource from the remote_certificate text string
     257      $publickey = openssl_get_publickey($remote_certificate);
     258  
     259      if ( gettype($publickey) != 'resource' ) {
     260          // Remote certificate is faulty.
     261          return false;
     262      }
     263  
     264      // Initialize vars
     265      $encryptedstring = '';
     266      $symmetric_keys = array();
     267  
     268      //        passed by ref ->     &$encryptedstring &$symmetric_keys
     269      $bool = openssl_seal($message, $encryptedstring, $symmetric_keys, array($publickey));
     270      $message = $encryptedstring;
     271      $symmetrickey = array_pop($symmetric_keys);
     272  
     273      $message = '<?xml version="1.0" encoding="iso-8859-1"?>
     274      <encryptedMessage>
     275          <EncryptedData Id="ED" xmlns="http://www.w3.org/2001/04/xmlenc#">
     276              <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#arcfour"/>
     277              <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
     278                  <ds:RetrievalMethod URI="#EK" Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/>
     279                  <ds:KeyName>XMLENC</ds:KeyName>
     280              </ds:KeyInfo>
     281              <CipherData>
     282                  <CipherValue>'.base64_encode($message).'</CipherValue>
     283              </CipherData>
     284          </EncryptedData>
     285          <EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#">
     286              <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
     287              <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
     288                  <ds:KeyName>SSLKEY</ds:KeyName>
     289              </ds:KeyInfo>
     290              <CipherData>
     291                  <CipherValue>'.base64_encode($symmetrickey).'</CipherValue>
     292              </CipherData>
     293              <ReferenceList>
     294                  <DataReference URI="#ED"/>
     295              </ReferenceList>
     296              <CarriedKeyName>XMLENC</CarriedKeyName>
     297          </EncryptedKey>
     298          <wwwroot>'.$mnet->wwwroot.'</wwwroot>
     299      </encryptedMessage>';
     300      return $message;
     301  }
     302  
     303  /**
     304   * Get your SSL keys from the database, or create them (if they don't exist yet)
     305   *
     306   * Get your SSL keys from the database, or (if they don't exist yet) call
     307   * mnet_generate_keypair to create them
     308   *
     309   * @param   string  $string     The text you want to sign
     310   * @return  string              The signature over that text
     311   */
     312  function mnet_get_keypair() {
     313      global $CFG, $DB;
     314      static $keypair = null;
     315      if (!is_null($keypair)) return $keypair;
     316      if ($result = get_config('mnet', 'openssl')) {
     317          list($keypair['certificate'], $keypair['keypair_PEM']) = explode('@@@@@@@@', $result);
     318          $keypair['privatekey'] = openssl_pkey_get_private($keypair['keypair_PEM']);
     319          $keypair['publickey']  = openssl_pkey_get_public($keypair['certificate']);
     320          return $keypair;
     321      } else {
     322          $keypair = mnet_generate_keypair();
     323          return $keypair;
     324      }
     325  }
     326  
     327  /**
     328   * Generate public/private keys and store in the config table
     329   *
     330   * Use the distinguished name provided to create a CSR, and then sign that CSR
     331   * with the same credentials. Store the keypair you create in the config table.
     332   * If a distinguished name is not provided, create one using the fullname of
     333   * 'the course with ID 1' as your organization name, and your hostname (as
     334   * detailed in $CFG->wwwroot).
     335   *
     336   * @param   array  $dn  The distinguished name of the server
     337   * @return  string      The signature over that text
     338   */
     339  function mnet_generate_keypair($dn = null, $days=28) {
     340      global $CFG, $USER, $DB;
     341  
     342      // check if lifetime has been overriden
     343      if (!empty($CFG->mnetkeylifetime)) {
     344          $days = $CFG->mnetkeylifetime;
     345      }
     346  
     347      $host = strtolower($CFG->wwwroot);
     348      $host = preg_replace("~^http(s)?://~",'',$host);
     349      $break = strpos($host.'/' , '/');
     350      $host   = substr($host, 0, $break);
     351  
     352      $site = get_site();
     353      $organization = $site->fullname;
     354  
     355      $keypair = array();
     356  
     357      $country  = 'NZ';
     358      $province = 'Wellington';
     359      $locality = 'Wellington';
     360      $email    = !empty($CFG->noreplyaddress) ? $CFG->noreplyaddress : 'noreply@'.$_SERVER['HTTP_HOST'];
     361  
     362      if(!empty($USER->country)) {
     363          $country  = $USER->country;
     364      }
     365      if(!empty($USER->city)) {
     366          $province = $USER->city;
     367          $locality = $USER->city;
     368      }
     369      if(!empty($USER->email)) {
     370          $email    = $USER->email;
     371      }
     372  
     373      if (is_null($dn)) {
     374          $dn = array(
     375             "countryName" => $country,
     376             "stateOrProvinceName" => $province,
     377             "localityName" => $locality,
     378             "organizationName" => $organization,
     379             "organizationalUnitName" => 'Moodle',
     380             "commonName" => substr($CFG->wwwroot, 0, 64),
     381             "subjectAltName" => $CFG->wwwroot,
     382             "emailAddress" => $email
     383          );
     384      }
     385  
     386      $dnlimits = array(
     387             'countryName'            => 2,
     388             'stateOrProvinceName'    => 128,
     389             'localityName'           => 128,
     390             'organizationName'       => 64,
     391             'organizationalUnitName' => 64,
     392             'commonName'             => 64,
     393             'emailAddress'           => 128
     394      );
     395  
     396      foreach ($dnlimits as $key => $length) {
     397          $dn[$key] = core_text::substr($dn[$key], 0, $length);
     398      }
     399  
     400      // ensure we remove trailing slashes
     401      $dn["commonName"] = preg_replace(':/$:', '', $dn["commonName"]);
     402      if (!empty($CFG->opensslcnf)) { //allow specification of openssl.cnf especially for Windows installs
     403          $new_key = openssl_pkey_new(array("config" => $CFG->opensslcnf));
     404      } else {
     405          $new_key = openssl_pkey_new();
     406      }
     407      if ($new_key === false) {
     408          // can not generate keys - missing openssl.cnf??
     409          return null;
     410      }
     411      if (!empty($CFG->opensslcnf)) { //allow specification of openssl.cnf especially for Windows installs
     412          $csr_rsc = openssl_csr_new($dn, $new_key, array("config" => $CFG->opensslcnf));
     413          $selfSignedCert = openssl_csr_sign($csr_rsc, null, $new_key, $days, array("config" => $CFG->opensslcnf));
     414      } else {
     415          $csr_rsc = openssl_csr_new($dn, $new_key, array('private_key_bits',2048));
     416          $selfSignedCert = openssl_csr_sign($csr_rsc, null, $new_key, $days);
     417      }
     418      unset($csr_rsc); // Free up the resource
     419  
     420      // We export our self-signed certificate to a string.
     421      openssl_x509_export($selfSignedCert, $keypair['certificate']);
     422      openssl_x509_free($selfSignedCert);
     423  
     424      // Export your public/private key pair as a PEM encoded string. You
     425      // can protect it with an optional passphrase if you wish.
     426      if (!empty($CFG->opensslcnf)) { //allow specification of openssl.cnf especially for Windows installs
     427          $export = openssl_pkey_export($new_key, $keypair['keypair_PEM'], null, array("config" => $CFG->opensslcnf));
     428      } else {
     429          $export = openssl_pkey_export($new_key, $keypair['keypair_PEM'] /* , $passphrase */);
     430      }
     431      openssl_pkey_free($new_key);
     432      unset($new_key); // Free up the resource
     433  
     434      return $keypair;
     435  }
     436  
     437  
     438  function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) {
     439      global $DB;
     440  
     441      $mnethost = $DB->get_record('mnet_host', array('id'=>$mnet_host_id));
     442      if ($aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnet_host_id))) {
     443          // Update.
     444          $aclrecord->accessctrl = $accessctrl;
     445          $DB->update_record('mnet_sso_access_control', $aclrecord);
     446  
     447          // Trigger access control updated event.
     448          $params = array(
     449              'objectid' => $aclrecord->id,
     450              'context' => context_system::instance(),
     451              'other' => array(
     452                  'username' => $username,
     453                  'hostname' => $mnethost->name,
     454                  'accessctrl' => $accessctrl
     455              )
     456          );
     457          $event = \core\event\mnet_access_control_updated::create($params);
     458          $event->add_record_snapshot('mnet_host', $mnethost);
     459          $event->trigger();
     460      } else {
     461          // Insert.
     462          $aclrecord = new stdClass();
     463          $aclrecord->username = $username;
     464          $aclrecord->accessctrl = $accessctrl;
     465          $aclrecord->mnet_host_id = $mnet_host_id;
     466          $aclrecord->id = $DB->insert_record('mnet_sso_access_control', $aclrecord);
     467  
     468          // Trigger access control created event.
     469          $params = array(
     470              'objectid' => $aclrecord->id,
     471              'context' => context_system::instance(),
     472              'other' => array(
     473                  'username' => $username,
     474                  'hostname' => $mnethost->name,
     475                  'accessctrl' => $accessctrl
     476              )
     477          );
     478          $event = \core\event\mnet_access_control_created::create($params);
     479          $event->add_record_snapshot('mnet_host', $mnethost);
     480          $event->trigger();
     481      }
     482      return true;
     483  }
     484  
     485  function mnet_get_peer_host ($mnethostid) {
     486      global $DB;
     487      static $hosts;
     488      if (!isset($hosts[$mnethostid])) {
     489          $host = $DB->get_record('mnet_host', array('id' => $mnethostid));
     490          $hosts[$mnethostid] = $host;
     491      }
     492      return $hosts[$mnethostid];
     493  }
     494  
     495  /**
     496   * Inline function to modify a url string so that mnet users are requested to
     497   * log in at their mnet identity provider (if they are not already logged in)
     498   * before ultimately being directed to the original url.
     499   *
     500   * @param string $jumpurl the url which user should initially be directed to.
     501   *     This is a URL associated with a moodle networking peer when it
     502   *     is fulfiling a role as an identity provider (IDP). Different urls for
     503   *     different peers, the jumpurl is formed partly from the IDP's webroot, and
     504   *     partly from a predefined local path within that webwroot.
     505   *     The result of the user hitting this jump url is that they will be asked
     506   *     to login (at their identity provider (if they aren't already)), mnet
     507   *     will prepare the necessary authentication information, then redirect
     508   *     them back to somewhere at the content provider(CP) moodle (this moodle)
     509   * @param array $url array with 2 elements
     510   *     0 - context the url was taken from, possibly just the url, possibly href="url"
     511   *     1 - the destination url
     512   * @return string the url the remote user should be supplied with.
     513   */
     514  function mnet_sso_apply_indirection ($jumpurl, $url) {
     515      global $USER, $CFG;
     516  
     517      $localpart='';
     518      $urlparts = parse_url($url[1]);
     519      if($urlparts) {
     520          if (isset($urlparts['path'])) {
     521              $path = $urlparts['path'];
     522              // if our wwwroot has a path component, need to strip that path from beginning of the
     523              // 'localpart' to make it relative to moodle's wwwroot
     524              $wwwrootpath = parse_url($CFG->wwwroot, PHP_URL_PATH);
     525              if (!empty($wwwrootpath) and strpos($path, $wwwrootpath) === 0) {
     526                  $path = substr($path, strlen($wwwrootpath));
     527              }
     528              $localpart .= $path;
     529          }
     530          if (isset($urlparts['query'])) {
     531              $localpart .= '?'.$urlparts['query'];
     532          }
     533          if (isset($urlparts['fragment'])) {
     534              $localpart .= '#'.$urlparts['fragment'];
     535          }
     536      }
     537      $indirecturl = $jumpurl . urlencode($localpart);
     538      //If we matched on more than just a url (ie an html link), return the url to an href format
     539      if ($url[0] != $url[1]) {
     540          $indirecturl = 'href="'.$indirecturl.'"';
     541      }
     542      return $indirecturl;
     543  }
     544  
     545  function mnet_get_app_jumppath ($applicationid) {
     546      global $DB;
     547      static $appjumppaths;
     548      if (!isset($appjumppaths[$applicationid])) {
     549          $ssojumpurl = $DB->get_field('mnet_application', 'sso_jump_url', array('id' => $applicationid));
     550          $appjumppaths[$applicationid] = $ssojumpurl;
     551      }
     552      return $appjumppaths[$applicationid];
     553  }
     554  
     555  
     556  /**
     557   * Output debug information about mnet.  this will go to the <b>error_log</b>.
     558   *
     559   * @param mixed $debugdata this can be a string, or array or object.
     560   * @param int   $debuglevel optional , defaults to 1. bump up for very noisy debug info
     561   */
     562  function mnet_debug($debugdata, $debuglevel=1) {
     563      global $CFG;
     564      $setlevel = get_config('', 'mnet_rpcdebug');
     565      if (empty($setlevel) || $setlevel < $debuglevel) {
     566          return;
     567      }
     568      if (is_object($debugdata)) {
     569          $debugdata = (array)$debugdata;
     570      }
     571      if (is_array($debugdata)) {
     572          mnet_debug('DUMPING ARRAY');
     573          foreach ($debugdata as $key => $value) {
     574              mnet_debug("$key: $value");
     575          }
     576          mnet_debug('END DUMPING ARRAY');
     577          return;
     578      }
     579      $prefix = 'MNET DEBUG ';
     580      if (defined('MNET_SERVER')) {
     581          $prefix .= " (server $CFG->wwwroot";
     582          if ($peer = get_mnet_remote_client() && !empty($peer->wwwroot)) {
     583              $prefix .= ", remote peer " . $peer->wwwroot;
     584          }
     585          $prefix .= ')';
     586      } else {
     587          $prefix .= " (client $CFG->wwwroot) ";
     588      }
     589      error_log("$prefix $debugdata");
     590  }
     591  
     592  /**
     593   * Return an array of information about all moodle's profile fields
     594   * which ones are optional, which ones are forced.
     595   * This is used as the basis of providing lists of profile fields to the administrator
     596   * to pick which fields to import/export over MNET
     597   *
     598   * @return array(forced => array, optional => array)
     599   */
     600  function mnet_profile_field_options() {
     601      global $DB;
     602      static $info;
     603      if (!empty($info)) {
     604          return $info;
     605      }
     606  
     607      $excludes = array(
     608          'id',              // makes no sense
     609          'mnethostid',      // makes no sense
     610          'timecreated',     // will be set to relative to the host anyway
     611          'timemodified',    // will be set to relative to the host anyway
     612          'auth',            // going to be set to 'mnet'
     613          'deleted',         // we should never get deleted users sent over, but don't send this anyway
     614          'confirmed',       // unconfirmed users can't log in to their home site, all remote users considered confirmed
     615          'password',        // no password for mnet users
     616          'theme',           // handled separately
     617          'lastip',          // will be set to relative to the host anyway
     618      );
     619  
     620      // these are the ones that user_not_fully_set_up will complain about
     621      // and also special case ones
     622      $forced = array(
     623          'username',
     624          'email',
     625          'firstname',
     626          'lastname',
     627          'auth',
     628          'wwwroot',
     629          'session.gc_lifetime',
     630          '_mnet_userpicture_timemodified',
     631          '_mnet_userpicture_mimetype',
     632      );
     633  
     634      // these are the ones we used to send/receive (pre 2.0)
     635      $legacy = array(
     636          'username',
     637          'email',
     638          'auth',
     639          'deleted',
     640          'firstname',
     641          'lastname',
     642          'city',
     643          'country',
     644          'lang',
     645          'timezone',
     646          'description',
     647          'mailformat',
     648          'maildigest',
     649          'maildisplay',
     650          'htmleditor',
     651          'wwwroot',
     652          'picture',
     653      );
     654  
     655      // get a random user record from the database to pull the fields off
     656      $randomuser = $DB->get_record('user', array(), '*', IGNORE_MULTIPLE);
     657      foreach ($randomuser as $key => $discard) {
     658          if (in_array($key, $excludes) || in_array($key, $forced)) {
     659              continue;
     660          }
     661          $fields[$key] = $key;
     662      }
     663      $info = array(
     664          'forced'   => $forced,
     665          'optional' => $fields,
     666          'legacy'   => $legacy,
     667      );
     668      return $info;
     669  }
     670  
     671  
     672  /**
     673   * Returns information about MNet peers
     674   *
     675   * @param bool $withdeleted should the deleted peers be returned too
     676   * @return array
     677   */
     678  function mnet_get_hosts($withdeleted = false) {
     679      global $CFG, $DB;
     680  
     681      $sql = "SELECT h.id, h.deleted, h.wwwroot, h.ip_address, h.name, h.public_key, h.public_key_expires,
     682                     h.transport, h.portno, h.last_connect_time, h.last_log_id, h.applicationid,
     683                     a.name as app_name, a.display_name as app_display_name, a.xmlrpc_server_url
     684                FROM {mnet_host} h
     685                JOIN {mnet_application} a ON h.applicationid = a.id
     686               WHERE h.id <> ?";
     687  
     688      if (!$withdeleted) {
     689          $sql .= "  AND h.deleted = 0";
     690      }
     691  
     692      $sql .= " ORDER BY h.deleted, h.name, h.id";
     693  
     694      return $DB->get_records_sql($sql, array($CFG->mnet_localhost_id));
     695  }
     696  
     697  
     698  /**
     699   * return an array information about services enabled for the given peer.
     700   * in two modes, fulldata or very basic data.
     701   *
     702   * @param mnet_peer $mnet_peer the peer to get information abut
     703   * @param boolean   $fulldata whether to just return which services are published/subscribed, or more information (defaults to full)
     704   *
     705   * @return array  If $fulldata is false, an array is returned like:
     706   *                publish => array(
     707   *                    serviceid => boolean,
     708   *                    serviceid => boolean,
     709   *                ),
     710   *                subscribe => array(
     711   *                    serviceid => boolean,
     712   *                    serviceid => boolean,
     713   *                )
     714   *                If $fulldata is true, an array is returned like:
     715   *                servicename => array(
     716   *                   apiversion => array(
     717   *                        name           => string
     718   *                        offer          => boolean
     719   *                        apiversion     => int
     720   *                        plugintype     => string
     721   *                        pluginname     => string
     722   *                        hostsubscribes => boolean
     723   *                        hostpublishes  => boolean
     724   *                   ),
     725   *               )
     726   */
     727  function mnet_get_service_info(mnet_peer $mnet_peer, $fulldata=true) {
     728      global $CFG, $DB;
     729  
     730      $requestkey = (!empty($fulldata) ? 'fulldata' : 'mydata');
     731  
     732      static $cache = array();
     733      if (array_key_exists($mnet_peer->id, $cache)) {
     734          return $cache[$mnet_peer->id][$requestkey];
     735      }
     736  
     737      $id_list = $mnet_peer->id;
     738      if (!empty($CFG->mnet_all_hosts_id)) {
     739          $id_list .= ', '.$CFG->mnet_all_hosts_id;
     740      }
     741  
     742      $concat = $DB->sql_concat('COALESCE(h2s.id,0) ', ' \'-\' ', ' svc.id', '\'-\'', 'r.plugintype', '\'-\'', 'r.pluginname');
     743  
     744      $query = "
     745          SELECT DISTINCT
     746              $concat as id,
     747              svc.id as serviceid,
     748              svc.name,
     749              svc.offer,
     750              svc.apiversion,
     751              r.plugintype,
     752              r.pluginname,
     753              h2s.hostid,
     754              h2s.publish,
     755              h2s.subscribe
     756          FROM
     757              {mnet_service2rpc} s2r,
     758              {mnet_rpc} r,
     759              {mnet_service} svc
     760          LEFT JOIN
     761              {mnet_host2service} h2s
     762          ON
     763              h2s.hostid in ($id_list) AND
     764              h2s.serviceid = svc.id
     765          WHERE
     766              svc.offer = '1' AND
     767              s2r.serviceid = svc.id AND
     768              s2r.rpcid = r.id
     769          ORDER BY
     770              svc.name ASC";
     771  
     772      $resultset = $DB->get_records_sql($query);
     773  
     774      if (is_array($resultset)) {
     775          $resultset = array_values($resultset);
     776      } else {
     777          $resultset = array();
     778      }
     779  
     780      require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';
     781  
     782      $remoteservices = array();
     783      if ($mnet_peer->id != $CFG->mnet_all_hosts_id) {
     784          // Create a new request object
     785          $mnet_request = new mnet_xmlrpc_client();
     786  
     787          // Tell it the path to the method that we want to execute
     788          $mnet_request->set_method('system/listServices');
     789          $mnet_request->send($mnet_peer);
     790          if (is_array($mnet_request->response)) {
     791              foreach($mnet_request->response as $service) {
     792                  $remoteservices[$service['name']][$service['apiversion']] = $service;
     793              }
     794          }
     795      }
     796  
     797      $myservices = array();
     798      $mydata = array();
     799      foreach($resultset as $result) {
     800          $result->hostpublishes  = false;
     801          $result->hostsubscribes = false;
     802          if (isset($remoteservices[$result->name][$result->apiversion])) {
     803              if ($remoteservices[$result->name][$result->apiversion]['publish'] == 1) {
     804                  $result->hostpublishes  = true;
     805              }
     806              if ($remoteservices[$result->name][$result->apiversion]['subscribe'] == 1) {
     807                  $result->hostsubscribes  = true;
     808              }
     809          }
     810  
     811          if (empty($myservices[$result->name][$result->apiversion])) {
     812              $myservices[$result->name][$result->apiversion] = array('serviceid' => $result->serviceid,
     813                                                                      'name' => $result->name,
     814                                                                      'offer' => $result->offer,
     815                                                                      'apiversion' => $result->apiversion,
     816                                                                      'plugintype' => $result->plugintype,
     817                                                                      'pluginname' => $result->pluginname,
     818                                                                      'hostsubscribes' => $result->hostsubscribes,
     819                                                                      'hostpublishes' => $result->hostpublishes
     820                                                                      );
     821          }
     822  
     823          // allhosts_publish allows us to tell the admin that even though he
     824          // is disabling a service, it's still available to the host because
     825          // he's also publishing it to 'all hosts'
     826          if ($result->hostid == $CFG->mnet_all_hosts_id && $CFG->mnet_all_hosts_id != $mnet_peer->id) {
     827              $myservices[$result->name][$result->apiversion]['allhosts_publish'] = $result->publish;
     828              $myservices[$result->name][$result->apiversion]['allhosts_subscribe'] = $result->subscribe;
     829          } elseif (!empty($result->hostid)) {
     830              $myservices[$result->name][$result->apiversion]['I_publish'] = $result->publish;
     831              $myservices[$result->name][$result->apiversion]['I_subscribe'] = $result->subscribe;
     832          }
     833          $mydata['publish'][$result->serviceid] = $result->publish;
     834          $mydata['subscribe'][$result->serviceid] = $result->subscribe;
     835  
     836      }
     837  
     838      $cache[$mnet_peer->id]['fulldata'] = $myservices;
     839      $cache[$mnet_peer->id]['mydata'] = $mydata;
     840  
     841      return $cache[$mnet_peer->id][$requestkey];
     842  }
     843  
     844  /**
     845   * return an array of the profile fields to send
     846   * with user information to the given mnet host.
     847   *
     848   * @param mnet_peer $peer the peer to send the information to
     849   *
     850   * @return array (like 'username', 'firstname', etc)
     851   */
     852  function mnet_fields_to_send(mnet_peer $peer) {
     853      return _mnet_field_helper($peer, 'export');
     854  }
     855  
     856  /**
     857   * return an array of the profile fields to import
     858   * from the given host, when creating/updating user accounts
     859   *
     860   * @param mnet_peer $peer the peer we're getting the information from
     861   *
     862   * @return array (like 'username', 'firstname', etc)
     863   */
     864  function mnet_fields_to_import(mnet_peer $peer) {
     865      return _mnet_field_helper($peer, 'import');
     866  }
     867  
     868  /**
     869   * helper for {@see mnet_fields_to_import} and {@mnet_fields_to_send}
     870   *
     871   * @access private
     872   *
     873   * @param mnet_peer $peer the peer object
     874   * @param string    $key 'import' or 'export'
     875   *
     876   * @return array (like 'username', 'firstname', etc)
     877   */
     878  function _mnet_field_helper(mnet_peer $peer, $key) {
     879      $tmp = mnet_profile_field_options();
     880      $defaults = explode(',', get_config('moodle', 'mnetprofile' . $key . 'fields'));
     881      if ('1' === get_config('mnet', 'host' . $peer->id . $key . 'default')) {
     882          return array_merge($tmp['forced'], $defaults);
     883      }
     884      $hostsettings = get_config('mnet', 'host' . $peer->id . $key . 'fields');
     885      if (false === $hostsettings) {
     886          return array_merge($tmp['forced'], $defaults);
     887      }
     888      return array_merge($tmp['forced'], explode(',', $hostsettings));
     889  }
     890  
     891  
     892  /**
     893   * given a user object (or array) and a list of allowed fields,
     894   * strip out all the fields that should not be included.
     895   * This can be used both for outgoing data and incoming data.
     896   *
     897   * @param mixed $user array or object representing a database record
     898   * @param array $fields an array of allowed fields (usually from mnet_fields_to_{send,import}
     899   *
     900   * @return mixed array or object, depending what type of $user object was passed (datatype is respected)
     901   */
     902  function mnet_strip_user($user, $fields) {
     903      if (is_object($user)) {
     904          $user = (array)$user;
     905          $wasobject = true; // so we can cast back before we return
     906      }
     907  
     908      foreach ($user as $key => $value) {
     909          if (!in_array($key, $fields)) {
     910              unset($user[$key]);
     911          }
     912      }
     913      if (!empty($wasobject)) {
     914          $user = (object)$user;
     915      }
     916      return $user;
     917  }
    

    Search This Site: