You are here

field_encrypt.inc in Field Encryption 7

Encryption functionality for the Field encrypt module.

File

field_encrypt.inc
View source
<?php

/**
 * @file
 * Encryption functionality for the Field encrypt module.
 */

/**
 * Add encryption to a field.
 *
 * Called when the "Encrypt this field" setting is first applied.
 *
 * @param array $field_info
 *   The field definition.
 * @param bool $has_data
 *   Whether the field has data.
 */
function field_encrypt_do_encrypt($field_info, $has_data) {

  // Column spec for encrypted data.
  $spec = array(
    'type' => 'text',
    'size' => 'medium',
    'description' => 'The encrypted value for this entity field column.',
  );

  // Prepare field so the storage details are available.
  $cache = _field_info_field_cache();
  $cache
    ->flush();
  $field_info = $cache
    ->prepareField($field_info);
  foreach (array(
    FIELD_LOAD_CURRENT,
    FIELD_LOAD_REVISION,
  ) as $age) {
    $table = key($field_info['storage']['details']['sql'][$age]);
    if ($has_data) {

      // Get all the current entries.
      $data = db_select($table, 't')
        ->fields('t')
        ->execute()
        ->fetchAll();

      // Delete the existing unencrypted data.
      db_delete($table)
        ->execute();
    }

    // Alter field columns to allow for encrypted data.
    foreach ($field_info['storage']['details']['sql'][$age][$table] as $column => $field) {
      if (!isset($field_info['indexes'][$column])) {
        db_change_field($table, $field, $field, $spec);
      }
    }

    // Encrypt and re-insert the data.
    if ($has_data && isset($data)) {
      foreach ($data as $record) {
        $query = db_insert($table);
        foreach ($field_info['storage']['details']['sql'][$age][$table] as $column => $field) {

          // Don't re-write empty, NULL values.
          if (empty($record->{$field})) {
            unset($record->{$field});
          }

          // Don't encrypt indexes.
          if (isset($field_info['indexes'][$column])) {
            continue;
          }
          if (isset($record->{$field})) {
            $record->{$field} = field_encrypt_encrypt($record->{$field});
          }
        }
        $query
          ->fields((array) $record)
          ->execute();
      }
    }
  }
  drupal_set_message(t('%field_name is now being encrypted', array(
    '%field_name' => $field_info['field_name'],
  )));
}

/**
 * Remove encryption from a field.
 *
 * Called when the "Encrypt this field" setting is disabled.
 *
 * @param array $field_info
 *   The field definition.
 * @param bool $has_data
 *   Whether the field has data.
 */
function field_encrypt_un_encrypt($field_info, $has_data) {

  // Prepare field so the storage details are available.
  $cache = _field_info_field_cache();
  $cache
    ->flush();
  $field_info = $cache
    ->prepareField($field_info);
  foreach (array(
    FIELD_LOAD_CURRENT,
    FIELD_LOAD_REVISION,
  ) as $age) {
    $table = key($field_info['storage']['details']['sql'][$age]);
    if ($has_data) {

      // Get all the current entries.
      $data = db_select($table, 't')
        ->fields('t')
        ->execute()
        ->fetchAll();

      // Delete the existing encrypted data.
      db_delete($table)
        ->execute();
    }

    // Revert the field columns back to their original state.
    foreach ($field_info['storage']['details']['sql'][$age][$table] as $column => $field) {
      if (!isset($field_info['indexes'][$column])) {
        db_change_field($table, $field, $field, $field_info['columns'][$column]);
      }
    }

    // Decrypt and re-insert the data.
    if ($has_data && isset($data)) {
      foreach ($data as $record) {
        $query = db_insert($table);
        foreach ($field_info['storage']['details']['sql'][$age][$table] as $column => $field) {

          // Don't re-write empty or NULL values.
          if (empty($record->{$field})) {
            unset($record->{$field});
          }

          // Don't un-encrypt indexes.
          if (isset($field_info['indexes'][$column])) {
            continue;
          }
          if (isset($record->{$field})) {
            $record->{$field} = field_encrypt_decrypt($record->{$field});
          }
        }
        $query
          ->fields((array) $record)
          ->execute();
      }
    }
  }
  drupal_set_message(t('%field_name is no longer being encrypted', array(
    '%field_name' => $field_info['field_name'],
  )));
}

/**
 * Implements hook_field_storage_pre_update().
 */
function _field_encrypt_field_storage_pre_update($entity_type, $entity, &$skip_fields) {
  static $field_info = NULL;
  if ($field_info === NULL) {
    $field_info = field_info_field_by_ids();
  }
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  if (is_null($vid)) {
    $vid = $id;
  }
  $default_options = array(
    'default' => FALSE,
    'deleted' => FALSE,
    'language' => NULL,
  );
  $instances = _field_invoke_get_instances($entity_type, $bundle, $default_options);
  foreach ($instances as $instance) {

    // Get the field data.
    $field_id = $instance['field_id'];
    $current_field_info = $field_info[$field_id];

    // Are we encrypting this field?
    if (!isset($current_field_info['settings']['field_encrypt']['encrypt']) || !$current_field_info['settings']['field_encrypt']['encrypt']) {

      // Encryption setting is set to FALSE, skip it.
      continue;
    }
    $field_name = $instance['field_name'];
    $data_table = key($current_field_info['storage']['details']['sql'][FIELD_LOAD_CURRENT]);
    $revision_table = key($current_field_info['storage']['details']['sql'][FIELD_LOAD_REVISION]);

    // We're bypassing usual storage, mark field for skipping.
    $skip_fields[$field_id] = $current_field_info;

    // If there's nothing in the field, go no further.
    if (empty($entity->{$field_name})) {
      continue;
    }

    // Now we need to insert the data, we can't do multiple merges so we have to
    // do each language/delta separately.
    foreach ($entity->{$field_name} as $language => $items) {

      // Delete and Insert, like field_sql_storage_field_storage_write() does.
      db_delete($data_table)
        ->condition('entity_id', $id)
        ->condition('entity_type', $entity_type)
        ->condition('revision_id', $vid)
        ->execute();
      db_delete($revision_table)
        ->condition('entity_id', $id)
        ->condition('entity_type', $entity_type)
        ->condition('revision_id', $vid)
        ->execute();
      foreach ($items as $delta => $item) {
        $merge_keys = array(
          'entity_type' => $entity_type,
          'bundle' => $bundle,
          'deleted' => 0,
          'entity_id' => $id,
          'language' => $language,
          'delta' => $delta,
        );
        $fields = array(
          'revision_id' => $vid,
        );
        foreach ($current_field_info['storage']['details']['sql'][FIELD_LOAD_CURRENT][$data_table] as $column => $field) {
          if (isset($item[$column]) && !empty($item[$column])) {
            $fields[$field] = !isset($current_field_info['indexes'][$column]) ? field_encrypt_encrypt($item[$column]) : $item[$column];
          }
        }

        // Put data in data table.
        db_merge($data_table)
          ->key($merge_keys)
          ->fields($fields)
          ->execute();

        // Put data in revision table.
        $merge_keys['revision_id'] = $vid;
        unset($fields['revision_id']);
        if (!empty($fields)) {
          db_merge($revision_table)
            ->key($merge_keys)
            ->fields($fields)
            ->execute();
        }
      }
    }
  }
}

/**
 * Implements hook_field_storage_pre_load().
 */
function _field_encrypt_field_storage_pre_load($entity_type, $queried_entities, $age, &$skip_fields, $options) {
  static $field_info = NULL;
  if ($field_info === NULL) {
    $field_info = field_info_field_by_ids();
  }
  foreach ($queried_entities as $entity) {
    list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
    if (is_null($vid)) {
      $vid = $id;
    }
    $default_options = array(
      'default' => FALSE,
      'deleted' => FALSE,
      'language' => NULL,
    );
    $instances = _field_invoke_get_instances($entity_type, $bundle, $default_options);
    foreach ($instances as $instance) {

      // Get the field data.
      $field_id = $instance['field_id'];
      $current_field_info = $field_info[$field_id];

      // Are we encrypting this field?
      if (!isset($current_field_info['settings']['field_encrypt']['encrypt']) || !$current_field_info['settings']['field_encrypt']['encrypt']) {
        continue;
      }
      $field_name = $instance['field_name'];

      // Get the CURRENT or REVISION field table.
      $table = key($current_field_info['storage']['details']['sql'][$age]);

      // We're bypassing usual storage, mark field for skipping.
      $skip_fields[$field_id] = $current_field_info;

      // Now we need to fetch the encrypted data.
      $results = db_select($table, 'r')
        ->fields('r')
        ->condition('r.entity_type', $entity_type)
        ->condition('r.entity_id', $id)
        ->condition('r.revision_id', $vid)
        ->orderBy('r.language', 'ASC')
        ->orderBy('r.delta', 'ASC')
        ->execute()
        ->fetchAll();
      $field_data = array();
      foreach ($results as $result) {
        foreach ($current_field_info['storage']['details']['sql'][$age][$table] as $column => $field) {
          if (isset($result->{$field}) && !empty($result->{$field})) {
            $field_data[$result->language][$result->delta][$column] = !isset($current_field_info['indexes'][$column]) ? field_encrypt_decrypt($result->{$field}) : $result->{$field};
          }
        }
      }
      $entity->{$field_name} = $field_data;
    }
  }
}

/**
 * Encrypt raw message.
 *
 * @param string $raw
 *   The raw message to be encrypted.
 *
 * @return string
 *   The encrypted data.
 */
function field_encrypt_encrypt($raw) {
  try {
    $encrypted = encrypt($raw, array(
      'base64' => TRUE,
    ));
  } catch (Exception $e) {
    return null;
  }
  return utf8_encode($encrypted);
}

/**
 * Decrypt encrypted message.
 *
 * @param string $encrypted
 *   The encrypted data.
 *
 * @return mixed|string
 *   The raw message.
 */
function field_encrypt_decrypt($encrypted) {
  $encrypted = utf8_decode($encrypted);
  try {
    $decrypted = decrypt($encrypted, array(
      'base64' => TRUE,
    ));
  } catch (Exception $e) {
    return null;
  }
  return $decrypted;
}

Functions

Namesort descending Description
field_encrypt_decrypt Decrypt encrypted message.
field_encrypt_do_encrypt Add encryption to a field.
field_encrypt_encrypt Encrypt raw message.
field_encrypt_un_encrypt Remove encryption from a field.
_field_encrypt_field_storage_pre_load Implements hook_field_storage_pre_load().
_field_encrypt_field_storage_pre_update Implements hook_field_storage_pre_update().