API Docs for:
Show:

File: yui3-gallery/src/gallery-yql-rest-client-oauth/js/yql-rest-client-oauth.js

/**
 * This module is a REST client supporting OAuth signatures.
 * @module gallery-yql-rest-client-oauth
 */

/**
 * @class YQLRESTClient
 * @static
 */

/**
 * Sends a REST request.
 * @method request
 * @param {Object} params Request parameters object.
 * <dl>
 *     <dt>
 *         accept
 *     </dt>
 *     <dd>
 *         Specifies the type of content to send in the response using the
 *         Accept HTTP header.  This tells YQL what kind of data format you want
 *         returned, as well as how to parse it.
 *     </dd>
 *     <dt>
 *         content
 *     </dt>
 *     <dd>
 *         The body content of a POST or PUT request.  This can be an object or
 *         a string.  If an object is used, contentType is assumed to be
 *         application/x-www-form-urlencoded.
 *     </dd>
 *     <dt>
 *         contentType
 *     </dt>
 *     <dd>
 *         Specifies the content-type of the body content of a POST or PUT
 *         request.
 *     </dd>
 *     <dt>
 *         fallbackCharsets
 *     </dt>
 *     <dd>
 *         Overrides the list of fallback character sets, which is set to
 *         "utf-8, iso-8859-1" by default, for decoding the returned response.
 *         YQL attempts to decode the response using the character sets listed
 *         here when the response either does not specify the character set or
 *         specifies an incorrect character set that results in a failed
 *         decoding.  This value may be an array of strings or one string with
 *         comma separated values.
 *     </dd>
 *     <dt>
 *         forceCharset
 *     </dt>
 *     <dd>
 *         Forces YQL to use the character set specified. Using this overrides
 *         both the character set specified by the response and the fallback
 *         character sets. 
 *     </dd>
 *     <dt>
 *         headers
 *     </dt>
 *     <dd>
 *         Adds HTTP headers to the request.
 *     </dd>
 *     <dt>
 *         jsonCompat
 *     </dt>
 *     <dd>
 *         Set this value to 'new' to get "lossless" JSON when making a REST
 *         call to a Web service.  jsonCompat: 'new' must also be set in the
 *         yqlParams object.
 *     </dd>
 *     <dt>
 *         method
 *     </dt>
 *     <dd>
 *         The HTTP method to use.  Must be one of 'DELETE', 'GET', 'HEAD',
 *         'POST' or 'PUT'.
 *     </dd>
 *     <dt>
 *         oAuth
 *     </dt>
 *     <dd>
 *         oAuth is an object with the following members:
 *         <dl>
 *             <dt>
 *                 consumer
 *             </dt>
 *             <dd>
 *                 an object with two members: key and secret
 *             </dd>
 *             <dt>
 *                 signatureMethod
 *             </dt>
 *             <dd>
 *                 must be either 'HMAC-SHA1' or 'PLAINTEXT'
 *             </dd>
 *             <dt>
 *                 token
 *             </dt>
 *             <dd>
 *                 an optional object with three optional members: key,
 *                 verifier, and secret.
 *             </dd>
 *         </dl>
 *     </dd>
 *     <dt>
 *         query
 *     </dt>
 *     <dd>
 *         Query parameters to add to the request.
 *     </dd>
 *     <dt>
 *         timeout
 *     </dt>
 *     <dd>
 *         Specifies the request timeout in milliseconds. This is useful when
 *         you want to cancel requests that take longer than expected. 
 *     </dd>
 *     <dt>
 *         url
 *     </dt>
 *     <dd>
 *         Provides a URL endpoint to query.  The url scheme and host must be
 *         lower case.  If you are using the default port, there must not be a
 *         port specified in the url.  Querystring parameters must be defined in
 *         the query member and not part of the url string.
 *     </dd>
 * </dl>
 * @param {Function} callbackFunction The response object is the only parameter.
 * @param {Object} yqlParams (optional) Passes through to Y.YQL.
 * @param {Object} yqlOpts (optional) Passes through to Y.YQL.
 */

(function (Y) {
    'use strict';
    
    var _do = Y.Do,
        _DoPrevent = _do.Prevent,
        _yqlRestClient = Y.YQLRESTClient,
    
        _buildAuthorizationHeader,
        _each = Y.each,
        _encode,
        _floor = Math.floor,
        _hmacSha1_b64 = Y.YQLCrypto.hmacSha1_b64,
        _map = Y.Array.map,
        _normalizeParameters,
        _now = Y.Lang.now,
        _random = Math.random,
        _randomString,
        _request = _yqlRestClient.request;
    
    _do.before(function (params, callbackFunction, yqlParams, yqlOpts) {
        params = params || {};
        
        var oAuth = params.oAuth,
            oAuthConsumer,
            oAuthParams = {},
            oAuthSignatureMethod,
            oAuthToken,
            
            buildSecret,
            setAuthorizationHeader;
        
        if (!oAuth) {
            return;
        }
        
        buildSecret = function () {
            return _encode(oAuthConsumer.secret) + '&' + _encode(oAuthToken.secret);
        };
        
        setAuthorizationHeader = function (oAuthSignature) {
            oAuthParams.oauth_signature = oAuthSignature;
            params.headers = params.headers || {};
            params.headers.Authorization = _buildAuthorizationHeader(oAuthParams);
        };
        
        oAuthConsumer = oAuth.consumer || {};
        oAuthParams.oauth_consumer_key = oAuthConsumer.key || '';
        oAuthSignatureMethod = oAuth.signatureMethod;
        oAuthParams.oauth_signature_method = oAuthSignatureMethod;
        oAuthToken = oAuth.token || {};
        oAuthParams.oauth_token = oAuthToken.key || '';
        
        if (oAuthToken.verifier) {
            oAuthParams.oauth_verifier = oAuthToken.verifier;
        }
        
        oAuthParams.oauth_version = '1.0';
        
        switch (oAuthSignatureMethod) {
            case 'HMAC-SHA1':
                oAuthParams.oauth_nonce = _randomString();
                oAuthParams.oauth_timestamp = _floor(_now() / 1000);
                _hmacSha1_b64([
                    _encode(params.method.toUpperCase()),
                    // url scheme and host must be lowercase.
                    // default port numbers must not be included.
                    _encode(params.url),
                    _encode(_normalizeParameters(params.content, oAuthParams, params.query))
                ].join('&'), buildSecret(), function (oAuthSignature) {
                    setAuthorizationHeader(oAuthSignature);
                    _request(params, callbackFunction, yqlParams, yqlOpts);
                }, null, {
                    proto: 'https'
                });
                return new _DoPrevent('asynchronous');
            case 'PLAINTEXT':
                setAuthorizationHeader(buildSecret());
                return;
            default:
                throw 'Unknown OAuth Signature Method';
        }
    }, _yqlRestClient, 'request');
    
    /**
     * Creates the OAuth Authorization Header
     * @method _buildAuthorizationHeader
     * @param {Object} oAuthParams
     * @private
     * @returns {String}
     */
    _buildAuthorizationHeader = function (oAuthParams) {
        var authorizationHeader = [];
        _each(oAuthParams, function (value, key) {
            authorizationHeader.push(_encode(key) + '="' + _encode(value));
        });
        return 'OAuth '+ authorizationHeader.join('",') + '"';
    };
    
    /**
     * Performs the more strict version of URL Encode required by OAuth.
     * @method _encode
     * @param {String} string
     * @private
     * @returns {String}
     */
    _encode = function (string) {
        if (!string) {
            return '';
        }
        
        return encodeURIComponent(string).replace(/(\!)|(\')|(\()|(\))|(\*)/g, function (character) {
            return '%' + character.charCodeAt(0).toString(16).toUpperCase();
        });
    };
    
    /**
     * Performs paramater sorting and normalization required by OAuth.
     * @method _normalizeParameters
     * @param {Object} content Parameters from POST or PUT body content
     * @param {Object} oAuthParams Parameters from OAuth
     * @param {Object} query Parameters from the query string.
     * @private
     * @returns {String}
     */
    _normalizeParameters = function (content, oAuthParams, query) {
        var params = [];
        
        _each([
            content,
            oAuthParams,
            query
        ], function (paramObject) {
            _each(paramObject, function (value, key) {
                params.push([
                    _encode(key),
                    _encode(value)
                ]);
            });
        });
        
        params.sort(function (a, b) {
            if (a[0] < b[0]) {
                return -1;
            }
            if (a[0] > b[0]) {
                return 1;
            }
            if (a[1] < b[1]) {
                return -1;
            }
            if (a[1] > b[1]) {
                return 1;
            }
            return 0;
        });
        
        params = _map(params, function (param) {
            return param.join('=');
        });
        
        return params.join('&');
    };
    
    /**
     * Generates a random string containing digits and upper-case letters.
     * @method _randomString
     * @private
     * @returns {String}
     */
    _randomString = function () {
        return _random().toString(32).substr(2);
    };
}(Y));