<?php

/**
 * @file
 * Handles the generation and validation of authentication tokens.
 *
 * These are to be used when dealing with applications such as Djatoka that do
 *  not pass through credentials.
 */

// Token lifespan(seconds): after this duration the token expires.
// 5 minutes.
define('ISLANDORA_AUTHTOKEN_TOKEN_TIMEOUT', 300);

/**
 * Request Islandora to construct an object/datastream authentication token.
 *
 * This token can later be turned in for access to the requested object or
 * datastream.
 *
 * @param string $pid
 *   The Fedora PID to generate the token for.
 * @param string $dsid
 *   The Fedora datastream ID to generate the token for.
 * @param int $uses
 *   Defaults to 1.
 *   The number of uses the token should be used for.  There are
 *   times when this should be greater than 1: ie. Djatoka needs
 *   to make two calls. This is the number of times it can be called
 *   from different php sessions, not in the run of a program. (it is
 *   statically cached).
 *
 * @return string
 *   The generated authentication token.
 */
function islandora_get_object_token($pid, $dsid, $uses = 1) {
  global $user;
  $time = time();
  // The function mt_rand is not considered cryptographically secure
  // and openssl_rando_pseudo_bytes() is only available in PHP > 5.3.
  // We might be safe in this case because mt_rand should never be using
  // the same seed, but this is still more secure.
  $token = hash("sha256", mt_rand() . $time);

  $id = db_insert("islandora_authtokens")->fields(
    array(
      'token' => $token,
      'uid' => $user->uid,
      'pid' => $pid,
      'dsid' => $dsid,
      'time' => $time,
      'remaining_uses' => $uses,
    ))->execute();

  return $token;
}

/**
 * Submit a token to islandora for authentication.
 *
 * Supply islandora with the token and the object/datastream it is for and you
 * will receive access if authentication passes. Tokens can only be redeemed
 * in a short window after their creation.
 *
 * @param string $pid
 *   The PID of the object to retrieve.
 * @param string $dsid
 *   The datastream id to retrieve.
 * @param string $token
 *   The registered token that allows access to this object.
 *
 * @return mixed
 *   The user credentials for access if the token validation passes,
 *   FALSE otherwise
 */
function islandora_validate_object_token($pid, $dsid, $token) {
  static $accounts = array();

  if (!empty($accounts[$pid][$dsid][$token])) {
    return $accounts[$pid][$dsid][$token];
  }

  // Check for database token.
  $time = time();
  $query = db_select('islandora_authtokens', 'tokens');
  $query->join('users', 'u', 'tokens.uid = u.uid');
  // The results will look like user objects.
  $result = $query
    ->fields('u', array('uid', 'name', 'pass'))
    ->fields('tokens', array('remaining_uses'))
    ->condition('token', $token, '=')
    ->condition('pid', $pid, '=')
    ->condition('dsid', $dsid, '=')
    ->condition('time', $time, '<=')
    ->condition('time', $time - ISLANDORA_AUTHTOKEN_TOKEN_TIMEOUT, '>')
    ->execute()
    ->fetchAll();
  if ($result) {
    $remaining_uses = $result[0]->remaining_uses;
    $remaining_uses--;
    // Remove the authentication token so it can't be used again.
    if ($remaining_uses == 0) {
      db_delete("islandora_authtokens")
        ->condition('token', $token, '=')
        ->condition('pid', $pid, '=')
        ->condition('dsid', $dsid, '=')
        ->execute();
    }
    // Decrement authentication token uses.
    else {
      db_update("islandora_authtokens")
      ->fields(array('remaining_uses' => $remaining_uses))
      ->condition('token', $token, '=')
      ->condition('pid', $pid, '=')
      ->condition('dsid', $dsid, '=')
      ->execute();
    }
    unset($result[0]->remaining_uses);
    $accounts[$pid][$dsid][$token] = $result[0];
  }
  else {
    $accounts[$pid][$dsid][$token] = FALSE;
  }

  return $accounts[$pid][$dsid][$token];
}

/**
 * Will remove any expired authentication tokens.
 */
function islandora_remove_expired_tokens() {
  $time = time();
  db_delete("islandora_authtokens")
    ->condition('time', $time - ISLANDORA_AUTHTOKEN_TOKEN_TIMEOUT, '<')
    ->execute();
}