<?php
// $Id$

/**
 * @file
 * Contains interface definitions and helper methods to use within adapter to
 * integrate with Drupal.
 */

/**
 * Helper function for authentication modules. Either logs in or registers
 * the current user, based on username. Either way, the global $user object is
 * populated and login tasks are performed.
 *
 * @param $oauth
 *   The oauth object which defines the application.
 *  @param $authname
 *   The unique authname from the provider.
 * @param $response
 *   The response from the provider.
 * @param $aid
 *   The authmap ID passed by value.
 *
 */
function oauth_authenticate($oauth, $authname, $response, &$aid) {
  if (user_is_anonymous()) {
    list($aid, $account) = oauth_external_load($oauth->oid, $authname);
    if (!$account) {
      // Register this new user.
      $userinfo = array(
        'name' => $authname,
        'pass' => user_password(),
        'init' => $authname,
        'status' => 1,
        'access' => REQUEST_TIME
      );
      $account = user_save(drupal_anonymous_user(), $userinfo);
      // Terminate if an error occurred during user_save().
      if (!$account) {
        drupal_set_message(t("Error saving user account."), 'error');
        return;
      }
      $aid = oauth_set_authmaps($oauth->oid, $account->uid, $authname);
    }

    // Log user in.
    $form_state['uid'] = $account->uid;
    user_login_submit(array(), $form_state);
  }
  else {
    $account = $GLOBALS['user'];
    $aid = oauth_set_authmaps($oauth->oid, $account->uid, $authname, $aid);
  }

  oauth_token_save($oauth, $aid, $response);
}

/**
 * Fetches a user object based on an external authentication source.
 *
 * @param $oid
 *   The oauth entity ID.
 * @param $authname
 *   The external authentication username.
 *
 * @return
 *   A fully-loaded user object if the user is found or FALSE if not found.
 */
function oauth_external_load($oid, $authname) {
  $result = db_select('oauthmap')
    ->fields('oauthmap', array('aid', 'uid'))
    ->condition('oid', $oid)
    ->condition('authname', $authname)
    ->execute()
    ->fetchObject();

  if ($result->uid) {
    return array($result->aid, user_load($result->uid));
  }
  else {
    return array(FALSE, FALSE);
  }
}

/**
* Authenticate from an OAuth response.
*
* @deprecated
*
* @param $module
* 	 Module implementing the OAuth.
* @param $oid
*   The oauth entity id.
* @param $uid
* 	 The user id.
* @param $response
*   The response from the provider.
*/
function oauth_auth($module, $oid, $response) {
  $type_info = oauth_type_get_type($module);
  $authname = $response[$type_info->mapping['authname']];
  $username = $response[$type_info->mapping['username']];

  $account = user_external_load("$authname@$oid"); // '@module' is because {authmap}.authname is unique..!
  if (isset($account->uid)) {
    if (!variable_get('user_email_verification', TRUE) || $account->login) {
      // Check if user is blocked.
      $state['values']['name'] = $account->name;
      user_login_name_validate(array(), $state);
      if (!form_get_errors()) {
        // Load global $user and perform final login tasks.
        $form_state['uid'] = $account->uid;
        user_login_submit(array(), $form_state);

        oauth_user_save($module, $oid, $account->uid, $response);
        // TODO: token update.
      }
    }
    else {
      drupal_set_message(
      t('You must validate your email address for this account before logging in via @module.',
      array('@module' => $type_info->name)
      ));
    }
  }
  elseif (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) {
    // Register new user.

    $form_state['values'] = array();
    $form_state['values']['op'] = t('Create new account');

    // Save response for use in MODULE_form_user_register_form_alter().
    $_SESSION[$module]['oid'] = $oid;
    $_SESSION[$module]['response'] = $response;

    drupal_form_submit('user_register_form', $form_state);
    // TODO: token create in the user insert hook.

//    if (!empty($form_state['user']))

    $message = drupal_get_messages('error');
    if (empty($form_state['values']['name']) || empty($form_state['values']['mail'])) {
      // If the Service provider did not provide both a user name and an email
      // address, ask the user to complete the registration manually instead of
      // showing the error messages about the missing values generated by FAPI.
      drupal_set_message(t('Complete the registration by filling out the form below. If you already have an account, you can <a href="@login">log in</a> now and add your @module under "My account".', array('@login' => url('user/login'), '@module' => ucfirst($module))), 'warning');
    }
    else {
      drupal_set_message(t('Account registration using the information provided by your @module provider failed due to the reasons listed below. Complete the registration by filling out the form below. If you already have an account, you can <a href="@login">log in</a> now and add your @module under "My account".', array('@login' => url('user/login'), '@module' => ucfirst($module))), 'warning');
      // Append form validation errors below the above warning.
      foreach ($messages['error'] as $message) {
        drupal_set_message($message, 'error');
      }
    }

    // We were unable to register a valid new user. Redirect to the normal
    // registration page and prefill with the values we received.
    drupal_goto('user/register');
  }
  else {
    drupal_set_message(t('Only site administrators can create new user accounts.'), 'error');
  }
  drupal_goto();
}

/**
 * Save (insert or update) changes to a user token or add a new token for a
 * given application.
 *
 * @param $oid
 *   The oauth ID of the application which controls the external identity.
 * @param $uid
 *   The user ID associated with the external identity.
 * @param $authname
 *   The unique authentication name.
 * @param $aid
 *   To indicate that this is a new record to be inserted, omit this argument.
 *   If this is an update, this argument specifies the primary key: aid.
 *
 * @return
 *   The last insert ID of the query, if one exists.
 */
function oauth_set_authmaps($oid, $uid, $authname, $aid = NULL) {
  $fields = array(
    'uid' => $uid,
    'oid' => $oid,
    'authname' => $authname,
  );

  if (is_null($aid)) {
    $aid = db_insert('oauthmap')
      ->fields($fields)
      ->execute();
  }
  else {
    db_update('oauthmap')
      ->condition('aid', $aid)
      ->fields($fields)
      ->execute();
  }

  return $aid;
}

function oauth_token_save($oauth, $aid, $token) {
  $table = oauth_token_table_get_name($oauth);
  $fields = array(
    'token' => $token['oauth_token'],
    'secret' => $token['oauth_token_secret'],
  );
  db_merge($table)
    ->key(array('aid' => $aid))
    ->fields($fields)
    ->execute();
}

/**
 * Load a specific token or a default token for a given application type.
 *
 * @param $oauth
 *   The oauth object which defines the application.
 * @param $aid
 *   The authmap ID to load the token for.
 *
 * @return
 *   Return an associative array with the following keys:
 *   - token: OAuth token key.
 *   - secret: OAuth token secret.
 *
 */
function oauth_token_load($oauth, $aid) {
  return db_select(oauth_token_table_get_name($oauth), 'token')
    ->fields('token', array('token', 'secret'))
    ->condition('aid', $aid)
    ->execute()
    ->fetchAssoc();
}

/**
 * Resets the cached information about oauth types.
 */
function oauth_info_cache_clear() {
  drupal_static_reset('oauth_get_info');
  // Clear all languages.
  cache_clear_all('oauth_info:', 'cache', TRUE);
}

interface DrupalOAuthConsumer {

  /**
   * Constructor.
   *
   * @param $oauth
   *	 The oauth entity for which the object is created.
   *
   * @param $uid
   *   The authmap aid for which this object is created.
   *
   *  TODO: Use direct method __construct when the PECL module can be extended.
   *  TODO: And released for Windows.
   *  @see: http://pecl.php.net/bugs/bug.php?id=21406
   */
  static function construct($oauth, $aid = NULL);

  /**
   * Finalize the login after a successful $response from the provider.
   *
   * @param $authname
   *   The unique authname from the provider.
   * @param $response
   *   The response from the provider.
   */
  function authenticate($authname, $response);

  /**
   * Menu callback access; Cross Site Request Forgery (CSRF) protection.
   */
  function checkAccess();

  /**
   * Turns off verbose request information (off by default).
   */
  function disableDebug();

  /**
   * Disable redirects from being followed automatically, thus allowing the
   * request to be manually redirected.
   */
  function disableRedirects();

  /**
   * Turns off the usual SSL peer certificate and host checks, this is not for
   * production environments.
   */
  function disableSSLChecks();

  /**
   * Turns on verbose request information useful for debugging.
   */
  function enableDebug();

  /**
   * Follow and sign redirects automatically, which is enabled by default.
   */
  function enableRedirects();

  /**
   * Turns on the usual SSL peer certificate and host checks (enabled by default).
   */
  function enableSSLChecks();

  /**
   * Fetch a resource.
   *
   * @param $url
   * 	 URL to the OAuth protected resource.
   * @param $extra
   * 	 Extra parameters to send with the request for the resource.
   * @param $http_method
   *   One of the OAUTH_HTTP_METHOD_* OAUTH constants, which includes GET, POST,
   *   PUT, HEAD, or DELETE.
   *
   *   HEAD (OAUTH_HTTP_METHOD_HEAD) can be useful for discovering information
   *   prior to the request (if OAuth credentials are in the Authorization header).
   * @param $http_headers
   * 	 HTTP client headers (such as User-Agent, Accept, etc.)
   *
   * @return
   * 	 Returns TRUE on success or FALSE on failure.
   */
  function fetch($url, $extra = NULL, $http_method = NULL, $http_headers = array());

  /**
   * Fetch an access token, secret and any additional response parameters from
   * the service provider.
   *
   * @param $access_url
   * 	 URL to the access token API.
   * @param $auth_session_handle
   *   Authorization session handle, this parameter does not have any citation
   *   in the core OAuth 1.0 specification but may be implemented by large
   *   providers. @see http://oauth.pbwiki.com/ScalableOAuth/
   *   for more information.
   */
  function getAccessToken($access_url, $auth_session_handle = NULL);

  /**
   * Gets the Certificate Authority information, which includes the ca_path and
   * ca_info set by OAuth::setCaPath().
   */
  function getCAPath();

  /**
   * Get the raw response of the most recent request.
   */
  function getLastResponse();

  /**
   * Get HTTP information about the last response.
   */
  function getLastResponseInfo();

  /**
   * Fetch a request token, secret and any additional response parameters from
   * the service provider. Sets a random number associated with the request to
   * avoid unauthorized call on the callback URL. This mecanism can be improved
   * by redefining this method on a subclass.
   *
   * The response contains the following parameters:
   * 	- oauth_token:
   * 			The Request Token.
   *  - oauth_token_secret:
   *  		The Token Secret.
   *  - Additional parameters:
   *  		Any additional parameters, as defined by the Service Provider.
   *
   * @param $request_url
   *   URL to the request token API.
   * @param $callback
   *   Optinal OAuth callback URL. Default to 'oauth/module/oauth_callback'
   *
   *   TODO: put back module/oauth_callback when http://drupal.org/node/1087940
   *   get fixed.
   *
   * @return
   *   Returns TRUE if a parameter is correctly set, otherwise FALSE (e.g., if
   *   an invalid auth_type is passed in.)
   */
  function getRequestToken($request_url, $callback = NULL);

  /**
   * Set where the OAuth parameters should be passed.
   *
   * @param $auth_type
   * auth_type can be one of the following flags (in order of decreasing
   * preference as per OAuth 1.0 section 5.2):
   *
   *   OAUTH_AUTH_TYPE_AUTHORIZATION
   * 		 Pass the OAuth parameters in the HTTP Authorization header.
   * 	 OAUTH_AUTH_TYPE_FORM
   * 		 Append the OAuth parameters to the HTTP POST request body.
   *   OAUTH_AUTH_TYPE_URI
   * 	   Append the OAuth parameters to the request URI.
   *   OAUTH_AUTH_TYPE_NONE
   * 	   None.
   */
  function setAuthType($auth_type);

  /**
   * Sets the Certificate Authority (CA), both for path and info.
   *
   * @param $ca_path
   * 	 The CA Path being set.
   * @param $ca_info
   *   The CA Info being set.
   *
   * @return
   *   Returns TRUE on success, or FALSE if either ca_path or ca_info are
   *   considered invalid.
   */
  function setCAPath($ca_path, $ca_info);

  /**
   * Sets the nonce for all subsequent requests.
   *
   * @param $nonce
   *   The value for oauth_nonce.
   *
   * @return
   *   Returns TRUE on success, or FALSE if the nonce is considered invalid.
   */
  function setNonce($nonce);

  /**
   * Sets the Request Engine, that will be sending the HTTP requests.
   * PECL OAuth >= 1.0.0
   *
   * @param $reqengine
   *   The desired request engine. Set to OAUTH_REQENGINE_STREAMS to use PHP
   *   Streams, or OAUTH_REQENGINE_CURL to use Curl.
   */
  function setRequestEngine($reqengine);

  /**
   * Sets the RSA certificate.
   * PECL OAuth >= 1.0.0
   *
   * @param $cert
   *   The RSA certificate.
   *
   * @return
   *   Returns TRUE on success, or FALSE on failure (e.g., the RSA certificate
   *   cannot be parsed.)
   */
  function setRSACertificate($cert);

  /**
   * Sets the OAuth timestamp for subsequent requests.
   * PECL OAuth >= 1.0.0
   *
   * @param $timestamp
   *   The timestamp.
   *
   * @return
   *   Returns TRUE, unless the timestamp is invalid, in which case FALSE is
   *   returned.
   */
  function setTimestamp($timestamp);

  /**
   * Set the token and secret for subsequent requests.
   *
   * @param $token
   *   The OAuth token.
   * @param $token_secret
   *   The OAuth token secret.
   *
   * @return
   *   TRUE
   */
  function setToken($token, $token_secret);

  /**
   * Sets the OAuth version for subsequent requests
   *
   * @param $version
   *   OAuth version, default value is always "1.0"
   *
   * @return
   *   Returns TRUE on success or FALSE on failure.
   */
  function setVersion($version);
}

interface DrupalOAuth2Consumer {

}