Bibliothèque OAuth1.0

Envoyer des demandes OAuth1.0 signées

/**
 * Adds a OAuth1 object to the global scope. This can be used as follows:
 *
 * const urlFetch = OAuth1.withAccessToken(consumerKey, consumerSecret,
 *     accessToken, accessSecret);
 * const response = urlFetch.fetch(url, params, options);
 */
(function(scope) {
  /**
   * Creates an object to provide OAuth1-based requests to API resources.
   * @param {string} consumerKey
   * @param {string} consumerSecret
   * @param {string} accessToken
   * @param {string} accessSecret
   * @constructor
   */
  function OAuth1UrlFetchApp(
      consumerKey, consumerSecret, accessToken, accessSecret) {
    this.consumerKey_ = consumerKey;
    this.consumerSecret_ = consumerSecret;
    this.accessToken_ = accessToken;
    this.accessSecret_ = accessSecret;
  }

  /**
   * Sends a signed OAuth 1.0 request.
   * @param {string} url The URL of the API resource.
   * @param {?Object.<string>=} opt_params Map of parameters for the URL.
   * @param {?Object.<string>=} opt_options Options for passing to UrlFetchApp
   *     for example, to set the method to POST, or to include a form body.
   * @return {?Object} The resulting object on success, or null if a failure.
   */
  OAuth1UrlFetchApp.prototype.fetch = function(url, opt_params, opt_options) {
    const oauthParams = {
      'oauth_consumer_key': this.consumerKey_,
      'oauth_timestamp': parseInt(new Date().getTime() / 1000),
      'oauth_nonce': this.generateNonce_(),
      'oauth_version': '1.0',
      'oauth_token': this.accessToken_,
      'oauth_signature_method': 'HMAC-SHA1'
    };

    const method = 'GET';
    if (opt_options && opt_options.method) {
      method = opt_options.method;
    }
    if (opt_options && opt_options.payload) {
      const formPayload = opt_options.payload;
    }

    const requestString =
        this.generateRequestString_(oauthParams, opt_params, formPayload);
    const signatureBaseString =
        this.generateSignatureBaseString_(method, url, requestString);
    const signature = Utilities.computeHmacSignature(
        Utilities.MacAlgorithm.HMAC_SHA_1, signatureBaseString,
        this.getSigningKey_());
    const b64signature = Utilities.base64Encode(signature);

    oauthParams['oauth_signature'] = this.escape_(b64signature);
    const fetchOptions = opt_options || {};
    fetchOptions['headers'] = {
      Authorization: this.generateAuthorizationHeader_(oauthParams)
    };
    if (fetchOptions.payload) {
      fetchOptions.payload = this.escapeForm_(fetchOptions.payload);
    }
    return UrlFetchApp.fetch(
        this.joinUrlToParams_(url, opt_params), fetchOptions);
  };

  /**
   * Concatenates request URL to parameters to form a single string.
   * @param {string} url The URL of the resource.
   * @param {?Object.<string>=} opt_params Optional key/value map of parameters.
   * @return {string} The full path built out with parameters.
   */
  OAuth1UrlFetchApp.prototype.joinUrlToParams_ = function(url, opt_params) {
    if (!opt_params) {
      return url;
    }
    const paramKeys = Object.keys(opt_params);
    const paramList = [];
    for (const key in opt_params) {
      paramList.push(`${key}=${opt_params[key]}`);
    }
    return `${url}?${paramList.join('&')}`;
  };

  /**
   * Generates a random nonce for use in the OAuth request.
   * @return {string} A random string.
   */
  OAuth1UrlFetchApp.prototype.generateNonce_ = function() {
    return Utilities
        .base64Encode(Utilities.computeDigest(
            Utilities.DigestAlgorithm.SHA_1,
            parseInt(Math.floor(Math.random() * 10000))))
        .replace(/[\/=_+]/g, '');
  };

  /**
   * Creates a properly-formatted string from a map of key/values from a form
   * post.
   * @param {!Object.<string>} payload Map of key/values.
   * @return {string} The formatted string for the body of the POST message.
   */
  OAuth1UrlFetchApp.prototype.escapeForm_ = function(payload) {
    const escaped = [];
    for (const key in payload) {
      escaped.push(`${this.escape_(key)}=${this.escape_(payload[key])}`);
    }
    return escaped.join('&');
  };

  /**
   * Returns a percent-escaped string for use with OAuth. Note that
   * encodeURIComponent is not sufficient for this as the Twitter API expects
   * characters such as exclamation-mark to be encoded. See:
   *     https://dev.twitter.com/discussions/12378
   * @param {string} str The string to be escaped.
   * @return {string} The escaped string.
   */
  OAuth1UrlFetchApp.prototype.escape_ = function(str) {
    return encodeURIComponent(str).replace(/[!*()']/g, function(v) {
      return '%' + v.charCodeAt().toString(16);
    });
  };

  /**
   * Generates the Authorization header using the OAuth parameters and
   * calculated signature.
   * @param {!Object} oauthParams A map of the required OAuth parameters. See:
   *     https://dev.twitter.com/oauth/overview/authorizing-requests
   * @return {string} An Authorization header value for use in HTTP requests.
   */
  OAuth1UrlFetchApp.prototype.generateAuthorizationHeader_ = function(
      oauthParams) {
    const params = [];
    for (const key in oauthParams) {
      params.push(`${key}="${oauthParams[key]}"`);
    }
    return `OAuth ${params.join(', ')}`;
  };

  /**
   * Generates the signature string for the request.
   * @param {string} method The HTTP method e.g. GET, POST
   * @param {string} The URL.
   * @param {string} requestString The string representing the parameters to the
   *     API call as constructed by generateRequestString.
   * @return {string} The signature base string. See:
   *     https://dev.twitter.com/oauth/overview/creating-signatures
   */
  OAuth1UrlFetchApp.prototype.generateSignatureBaseString_ = function(
      method, url, requestString) {
    return [method, this.escape_(url), this.escape_(requestString)].join('&');
  };

  /**
   * Generates the key for signing the OAuth request
   * @return {string} The signing key.
   */
  OAuth1UrlFetchApp.prototype.getSigningKey_ = function() {
    return this.escape_(this.consumerSecret_) + '&' +
        this.escape_(this.accessSecret_);
  };

  /**
   * Generates the request string for signing, as used to produce a signature
   * for the Authorization header. see:
   * https://dev.twitter.com/oauth/overview/creating-signatures
   * @param {!Object} oauthParams The required OAuth parameters for the request,
   *     see: https://dev.twitter.com/oauth/overview/authorizing-requests
   * @param {?Object=} opt_params Optional parameters specified as part of the
   *     request, in map form, for example to specify /path?a=b&c=d&e=f... etc
   * @param {?Object=} opt_formPayload Optional mapping of pairs used in a form
   *     as part of a POST request.
   * @return {string} The request string
   */
  OAuth1UrlFetchApp.prototype.generateRequestString_ = function(
      oauthParams, opt_params, opt_formPayload) {
    const requestParams = {};
    const requestPath = [];
    for (let i = 0; i < arguments.length; i++) {
      const mapping = arguments[i];
      if (mapping) {
        const paramKeys = Object.keys(mapping);
        for (let j = 0, paramKey; paramKey = paramKeys[j]; j++) {
          requestParams[paramKey] = mapping[paramKey];
        }
      }
    }
    for (const key in requestParams) {
      requestPath.push(`${this.escape_(key)}=${this.escape_(requestParams[key])}`);
    }
    return requestPath.join('&');
  };

  /**
   * Builds a OAuth1UrlFetchApp object based on supplied access token (and other
   * parameters.
   * @param {string} consumerKey
   * @param {string} consumerSecret
   * @param {string} accessToken
   * @param {string} accessSecret
   * @return {!OAuth1UrlFetchApp}
   */
  function withAccessToken(
      consumerKey, consumerSecret, accessToken, accessSecret) {
    return new OAuth1UrlFetchApp(
        consumerKey, consumerSecret, accessToken, accessSecret);
  }

  scope.OAuth1 = {withAccessToken: withAccessToken};
})(this);