You are here

radioactivity_http.module in Radioactivity 5

File

plugins/radioactivity_http.module
View source
<?php

function radioactivity_http_help($section = '') {
  $output = '';
  switch ($section) {
    case "admin/help#radioactivity":
      $output = '<p>' . t('Provides HTTP interface for some radioactivity functionality. The functionality is ' . 'exposed as individually configured <em>ports</em>. Please take note of security considerations when ' . 'exposing ports to internet. More info on that in the port configurator.') . '</p>';
      break;
  }
  return $output;
}
function _radioactivity_http_get_ports() {
  return variable_get('radioactivity_http_ports', array());
}
function radioactivity_http_menu($may_cache) {
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/radioactivity/http_ports',
      'title' => t('HTTP ports'),
      'description' => t('Configure HTTP ports for external access.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'radioactivity_http_ports_list',
      ),
      'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
      'weight' => 30,
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/settings/radioactivity/http_port',
      'title' => t('HTTP port editor'),
      'description' => t('Configure HTTP port for external access.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'radioactivity_http_port_edit',
      ),
      'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/settings/radioactivity/http_port_delete',
      'title' => t('Delete HTTP port'),
      'description' => t('Delete HTTP port for external access.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'radioactivity_http_port_delete',
      ),
      'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
      'type' => MENU_CALLBACK,
    );
  }
  else {
    foreach (_radioactivity_http_get_ports() as $id => $port) {
      $items[] = array(
        'path' => $port['path'],
        'type' => MENU_CALLBACK,
        'callback' => 'radioactivity_http_cb',
        'callback arguments' => array(
          $id,
        ),
        'access' => TRUE,
      );
    }
  }
  return $items;
}
function radioactivity_http_ports_list() {
  $form = array();
  $form[] = array(
    '#value' => '<h2>' . t('Configured HTTP ports') . '</h2>',
  );

  // create the table
  $rows = array();
  $ports = _radioactivity_http_get_ports();
  if (count($ports)) {
    foreach ($ports as $id => $port) {
      $rows[] = array(
        check_plain($id),
        check_plain($port['path']),
        l(t('Edit'), 'admin/settings/radioactivity/http_port/' . $id) . ' ' . l(t('Delete'), 'admin/settings/radioactivity/http_port_delete/' . $id),
      );
    }
  }
  else {
    $rows[] = array(
      array(
        'colspan' => 3,
        'data' => t('No ports configured'),
      ),
    );
  }
  $table = theme('table', array(
    t('Port id'),
    t('Port URL'),
    t('Actions'),
  ), $rows);
  $form['ports'] = array(
    '#value' => $table,
  );
  $form['create_port'] = array(
    '#value' => l(t('Create new port'), 'admin/settings/radioactivity/http_port/new'),
  );
  return $form;
}
function radioactivity_http_port_delete($port_id) {
  $form = array();
  drupal_set_title(t('Confirm delete port %id', array(
    '%id' => $port_id,
  )));
  $form['port_id'] = array(
    '#type' => 'hidden',
    '#default_value' => $port_id,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Delete'),
  );
  $form['cancel'] = array(
    '#value' => l(t('Cancel'), 'admin/settings/radioactivity/http_ports'),
  );
  return $form;
}
function radioactivity_http_port_delete_submit($form_id, $form) {
  $port_id = $form['port_id'];
  drupal_set_message(t('Deleted port %id', array(
    '%id' => $port_id,
  )));
  $ports = _radioactivity_http_get_ports();
  unset($ports[$port_id]);
  variable_set('radioactivity_http_ports', $ports);
  drupal_goto('admin/settings/radioactivity/http_ports');
}
function radioactivity_http_port_edit($port_id) {
  global $base_url;
  $form = array();
  if ($port_id == 'new') {

    // this is a new port
    $form[] = array(
      '#value' => '<h3>' . t('Create new port') . '</h3>',
    );
  }
  else {
    $form[] = array(
      '#value' => '<h3>' . t('Edit port %id', array(
        '%id' => $port_id,
      )) . '</h3>',
    );
  }
  $ports = _radioactivity_http_get_ports();
  $port = $ports[$port_id];
  $form['port_id'] = array(
    '#type' => 'hidden',
    '#default_value' => $port_id,
  );
  $form['path'] = array(
    '#type' => 'textfield',
    '#required' => TRUE,
    '#title' => t('Path of the access point'),
    '#description' => t('Define the path of the port access point. The exposed methods will be accessed by ' . '<code>' . $base_url . '/&lt;path&gt;/&lt;method_name&gt;</code>. ' . 'Do not add trailing slash.'),
    '#default_value' => $port['path'],
  );
  $form['security_scheme'] = array(
    '#type' => 'select',
    '#title' => t('Security scheme for the port'),
    '#description' => t('Choose security scheme for the port. The available schemes are:') . '<dl>' . '<dt>' . t('None') . '</dt>' . '<dd>' . t('No security. Choose this only if you have secured the access some other way.') . '</dd>' . '<dt>' . t('MD5 sign with private key') . '</dt>' . '<dd>' . t('MD5-based private key signing of method access. The signature is created by ' . 'the following formula: ') . "<code>md5('&lt;method_name&gt;,&lt;arg1_name=arg1_value&gt;,...,&lt;argN_name=argN_value&gt;,&lt;pkey&gt')</code>. " . t('The lowercase signature is the appended to the query as a query parameter <code>s</code>. Note that ' . 'the parameters are listed in <em>signature</em> order which may differ from the actual invocation order.') . '</dl>',
    '#default_value' => $port['security_scheme'],
    '#options' => array(
      'none' => t('None'),
      'pkey-md5' => t('MD5 sign with private key'),
    ),
  );
  $form['private_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Private key'),
    '#description' => t('The private key is used by some security schemes.'),
    '#default_value' => $port['private_key'],
  );
  $form['access_method'] = array(
    '#type' => 'select',
    '#title' => t('Access method'),
    '#description' => t('Choose accessing method for the operations. The available access methods are:') . '<dl>' . '<dt>' . t('Method + query params') . '</dt>' . '<dd>' . t('Method is invoked by HTTP GET to <code>&lt;port_url&gt;/&lt;method_name&gt;?arg1_name=arg1_value&...argN_name=argN_value</code> .') . '</dd>' . '</dl>',
    '#default_value' => $port['access_method'],
    '#options' => array(
      'method-and-query' => t('Method + query params'),
    ),
  );
  $form['return'] = array(
    '#type' => 'select',
    '#title' => t('Return value encoding'),
    '#description' => t('Select return value encoding.'),
    '#default_value' => $port['return'],
    '#options' => array(
      'php-serialize' => t('PHP serialize()'),
    ),
  );
  $missing_functions = array();
  if (function_exists('json_encode')) {
    $form['return']['#options']['json'] = t('JSON');
  }
  else {
    $missing_functions[] = 'json_encode';
  }
  $options = array();
  foreach (_radioactivity_http_get_method_signatures() as $method => $signature) {
    $options[$method] = $method . '(' . implode(', ', $signature) . ')';
  }
  $form['exposed_methods'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Exposed method'),
    '#description' => t('Choose methods to expose by this port.'),
    '#options' => $options,
    '#default_value' => $port['exposed_methods'],
  );
  $form['missing_functions'] = array(
    '#type' => 'item',
    '#title' => t('Missing extension functions'),
    '#description' => t('Add extensions to provide missing functions if required. They ' . 'bring more configuration options.'),
  );
  if (!count($missing_functions)) {
    $form['missing_functions']['#value'] = t('None');
  }
  else {
    $form['missing_functions']['#value'] = implode(', ', $missing_functions);
  }
  $form['example'] = array(
    '#type' => 'item',
    '#title' => t("Example on how to invoke <code>radioactivity_get_energy(oid=2, oclass='node')</code>"),
    '#description' => t("The port translates the call to <code>radioactivity_get_energy(2, 'node')</code> as in PHP code. " . 'This retrieves radioactivity info on node/2. This example is not available before the port is saved. ' . 'Also, you must expose <code>radioactivity_get_energy</code> to make this example actually work.'),
  );
  if ($port_id == 'new') {
    $form['example']['#value'] = t('Example unavailable');
  }
  else {
    $query = array();
    switch ($port['access_method']) {
      case 'method-and-query':
        $base = $base_url . '/' . $port['path'] . '/radioactivity_get_energy';
        $query['oid'] = 2;
        $query['oclass'] = 'node';
        break;
    }
    switch ($port['security_scheme']) {
      case 'pkey-md5':
        $query['s'] = _radioactivity_http_create_pkey_md5('radioactivity_get_energy', array(
          'oid' => 2,
          'oclass' => 'node',
        ), $port['private_key']);
    }
    $url = $base;
    if (count($query)) {
      $url .= '?';
      $first = TRUE;
      foreach ($query as $name => $value) {
        if (!$first) {
          $url .= '&';
        }
        $url .= urlencode($name) . '=' . urlencode($value);
        $first = FALSE;
      }
    }
    $form['example']['#value'] = '<a href="' . $url . '">' . $url . '</a>';
  }
  $form['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  $form['save_and_edit'] = array(
    '#type' => 'submit',
    '#value' => t('Save and edit'),
  );
  $form['cancel'] = array(
    '#value' => l(t('Cancel'), 'admin/settings/radioactivity/http_ports'),
  );
  return $form;
}
function radioactivity_http_port_edit_submit($form_id, $form) {
  $ports = _radioactivity_http_get_ports();
  $port_id = $form['port_id'];
  if ($port_id == 'new') {

    // search new port id
    if (count($ports)) {
      $port_ids = array_keys($ports);
      sort($port_ids, SORT_NUMERIC);
      $port_id = 1 + $port_ids[count($ports) - 1];
    }
    else {
      $port_id = 1;
    }
  }
  $ports[$port_id] = array(
    'path' => $form['path'],
    'security_scheme' => $form['security_scheme'],
    'private_key' => $form['private_key'],
    'access_method' => $form['access_method'],
    'exposed_methods' => $form['exposed_methods'],
    'return' => $form['return'],
  );
  drupal_set_message(t('Saved port %id', array(
    '%id' => $port_id,
  )));
  variable_set('radioactivity_http_ports', $ports);
  if ($form['op'] == t('Save and edit')) {
    drupal_goto('admin/settings/radioactivity/http_port/' . $port_id);
  }
  else {
    drupal_goto('admin/settings/radioactivity/http_ports');
  }
}
function _radioactivity_http_get_method_signatures() {
  static $signatures = array(
    'radioactivity_get_energy' => array(
      'oid',
      'oclass',
    ),
    'radioactivity_add_energy' => array(
      'oid',
      'oclass',
      'source',
    ),
    'radioactivity_delete_energy' => array(
      'oid',
      'oclass',
    ),
  );
  return $signatures;
}
function _radioactivity_http_get_method_signature($method) {
  $signatures = _radioactivity_http_get_method_signatures();
  return $signatures[$method];
}
function _radioactivity_http_create_pkey_md5($method, $params, $pkey) {
  $param_string = '';
  $signature = _radioactivity_http_get_method_signature($method);
  $first = TRUE;
  foreach ($signature as $name) {
    if (!$first) {
      $param_string .= ',';
    }
    $param_string .= $name . '=' . $params[$name];
    $first = FALSE;
  }
  return md5($method . ',' . $param_string . ',' . $pkey);
}
function _radioactivity_http_cb_get_invocation($port) {
  switch ($port['access_method']) {
    case 'method-and-query':
      $method = substr($_GET['q'], strlen($port['path']) + 1);
      $params = array();
      $signature = _radioactivity_http_get_method_signature($method);
      if (!$signature) {
        return FALSE;
      }
      foreach ($signature as $name) {
        $params[$name] = $_GET[$name];
      }
      return array(
        'method' => $method,
        'params' => $params,
      );
  }
  return FALSE;
}
function _radioactivity_http_cb_process($port) {
  $invocation = _radioactivity_http_cb_get_invocation($port);

  // check that the method is actually accessible
  if (!$invocation) {
    print 'Unknown method';
    return;
  }
  if (!$port['exposed_methods'][$invocation['method']]) {
    print 'Method not exposed';
    return;
  }

  // security check
  switch ($port['security_scheme']) {
    case 'none':

      // ok, no checks necessary
      break;
    case 'pkey-md5':

      // pkey + md5
      $expected_hash = _radioactivity_http_create_pkey_md5($invocation['method'], $invocation['params'], $port['private_key']);
      break;
    default:
      print 'Unknown security scheme';
  }
  if (isset($expected_hash)) {
    if ($expected_hash != $_GET['s']) {
      print 'Hash signature mismatch';
      return;
    }
  }

  // make the invocation
  $ret = call_user_func_array($invocation['method'], $invocation['params']);

  // return result
  switch ($port['return']) {
    case 'json':
      print json_encode($ret);
      break;
    case 'php-serialize':
      print serialize($ret);
      break;
    default:
      print 'Unknown return encoding: ' . $invocation['return'];
  }
}
function radioactivity_http_cb($port_id) {
  $ports = _radioactivity_http_get_ports();
  $port = $ports[$port_id];
  unset($ports);
  _radioactivity_http_cb_process($port);
  module_invoke_all('exit', $url);
  exit;
}