Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace mod_bigbluebuttonbn;
  18  
  19  use Exception;
  20  use Firebase\JWT\JWT;
  21  use Firebase\JWT\Key;
  22  use mod_bigbluebuttonbn\local\config;
  23  
  24  /**
  25   * The broker routines
  26   *
  27   * @package   mod_bigbluebuttonbn
  28   * @copyright 2010 onwards, Blindside Networks Inc
  29   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
  31   */
  32  class broker {
  33  
  34      /** @var array List of required params */
  35      protected $requiredparams = [
  36          'recording_ready' => [
  37              'bigbluebuttonbn' => 'The BigBlueButtonBN instance ID must be specified.',
  38              'signed_parameters' => 'A JWT encoded string must be included as [signed_parameters].'
  39          ],
  40          'meeting_events' => [
  41              'bigbluebuttonbn' => 'The BigBlueButtonBN instance ID must be specified.'
  42          ],
  43      ];
  44  
  45      /**
  46       * Validate the supplied list of parameters, providing feedback about any missing or incorrect values.
  47       *
  48       * @param array $params
  49       * @return null|string
  50       */
  51      public function validate_parameters(array $params): ?string {
  52          if (!isset($params['action']) || empty($params['action']) ) {
  53              return 'Parameter ['.$params['action'].'] was not included';
  54          }
  55  
  56          $action = strtolower($params['action']);
  57          if (!array_key_exists($action, $this->requiredparams)) {
  58              return "Action {$params['action']} can not be performed.";
  59          }
  60          return $this->validate_parameters_message($params, $this->requiredparams[$action]);
  61      }
  62  
  63      /**
  64       * Check whether the specified parameter is valid.
  65       *
  66       * @param array $params
  67       * @param array $requiredparams
  68       * @return null|string
  69       */
  70      protected static function validate_parameters_message(array $params, array $requiredparams): ?string {
  71          foreach ($requiredparams as $param => $message) {
  72              if (!array_key_exists($param, $params) || $params[$param] == '') {
  73                  return $message;
  74              }
  75          }
  76  
  77          // Everything is valid.
  78          return null;
  79      }
  80  
  81      /**
  82       * Helper for responding when recording ready is performed.
  83       *
  84       * @param instance $instance
  85       * @param array $params
  86       */
  87      public static function process_recording_ready(instance $instance, array $params): void {
  88          // Decodes the received JWT string.
  89          try {
  90              $decodedparameters = JWT::decode(
  91                  $params['signed_parameters'],
  92                  new Key(config::get('shared_secret'), 'HS256')
  93              );
  94          } catch (Exception $e) {
  95              $error = 'Caught exception: ' . $e->getMessage();
  96              header('HTTP/1.0 400 Bad Request. ' . $error);
  97              return;
  98          }
  99  
 100          // Validations.
 101          if (!isset($decodedparameters->record_id)) {
 102              header('HTTP/1.0 400 Bad request. Missing record_id parameter');
 103              return;
 104          }
 105  
 106          $recording = recording::get_record(['recordingid' => $decodedparameters->record_id]);
 107          if (!isset($recording)) {
 108              header('HTTP/1.0 400 Bad request. Invalid record_id');
 109              return;
 110          }
 111  
 112          // Sends the messages.
 113          try {
 114              // We make sure messages are sent only once.
 115              if ($recording->get('status') != recording::RECORDING_STATUS_NOTIFIED) {
 116                  $task = new \mod_bigbluebuttonbn\task\send_recording_ready_notification();
 117                  $task->set_instance_id($instance->get_instance_id());
 118  
 119                  \core\task\manager::queue_adhoc_task($task);
 120  
 121                  $recording->set('status', recording::RECORDING_STATUS_NOTIFIED);
 122                  $recording->update();
 123              }
 124              header('HTTP/1.0 202 Accepted');
 125          } catch (Exception $e) {
 126              $error = 'Caught exception: ' . $e->getMessage();
 127              header('HTTP/1.0 503 Service Unavailable. ' . $error);
 128          }
 129      }
 130  
 131      /**
 132       * Process meeting events for instance with provided HTTP headers.
 133       *
 134       * @param instance $instance
 135       * @return void
 136       */
 137      public static function process_meeting_events(instance $instance) {
 138          try {
 139              // Get the HTTP headers.
 140              $authorization = self::get_authorization_token();
 141  
 142              // Pull the Bearer from the headers.
 143              if (empty($authorization)) {
 144                  $msg = 'Authorization failed';
 145                  header('HTTP/1.0 400 Bad Request. ' . $msg);
 146                  return;
 147              }
 148              // Verify the authenticity of the request.
 149              $token = \Firebase\JWT\JWT::decode(
 150                  $authorization[1],
 151                  new Key(config::get('shared_secret'), 'HS512')
 152              );
 153  
 154              // Get JSON string from the body.
 155              $jsonstr = file_get_contents('php://input');
 156  
 157              // Convert JSON string to a JSON object.
 158              $jsonobj = json_decode($jsonstr);
 159              $headermsg = meeting::meeting_events($instance, $jsonobj);
 160              header($headermsg);
 161          } catch (Exception $e) {
 162              $msg = 'Caught exception: ' . $e->getMessage();
 163              header('HTTP/1.0 400 Bad Request. ' . $msg);
 164          }
 165      }
 166  
 167  
 168      /**
 169       * Get authorisation token
 170       *
 171       * We could use getallheaders but this is only compatible with apache types of servers
 172       * some explanations and examples here: https://www.php.net/manual/en/function.getallheaders.php#127190
 173       *
 174       * @return array|null an array composed of the Authorization token provided in the header.
 175       */
 176      private static function get_authorization_token(): ?array {
 177          $autorization = null;
 178          if (isset($_SERVER['Authorization'])) {
 179              $autorization = trim($_SERVER["Authorization"]);
 180          } else if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
 181              $autorization = trim($_SERVER["HTTP_AUTHORIZATION"]);
 182          } else if (function_exists('apache_request_headers')) {
 183              $requestheaders = apache_request_headers();
 184              $requestheaders = array_combine(array_map('ucwords',
 185                      array_keys($requestheaders)), array_values($requestheaders));
 186  
 187              if (isset($requestheaders['Authorization'])) {
 188                  $autorization = trim($requestheaders['Authorization']);
 189              }
 190          }
 191          return empty($autorization) ? null : explode(" ", $autorization);
 192      }
 193  }