github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/clients/chrome/clip-it-good/chrome_ex_oauthsimple.js (about)

     1  /* OAuthSimple
     2    * A simpler version of OAuth
     3    *
     4    * author:     jr conlin
     5    * mail:       src@anticipatr.com
     6    * copyright:  unitedHeroes.net
     7    * version:    1.0
     8    * url:        http://unitedHeroes.net/OAuthSimple
     9    *
    10    * Copyright (c) 2009, unitedHeroes.net
    11    * All rights reserved.
    12    *
    13    * Redistribution and use in source and binary forms, with or without
    14    * modification, are permitted provided that the following conditions are met:
    15    *     * Redistributions of source code must retain the above copyright
    16    *       notice, this list of conditions and the following disclaimer.
    17    *     * Redistributions in binary form must reproduce the above copyright
    18    *       notice, this list of conditions and the following disclaimer in the
    19    *       documentation and/or other materials provided with the distribution.
    20    *     * Neither the name of the unitedHeroes.net nor the
    21    *       names of its contributors may be used to endorse or promote products
    22    *       derived from this software without specific prior written permission.
    23    *
    24    * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
    25    * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    26    * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    27    * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
    28    * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    29    * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    30    * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    31    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    32    * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    33    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    34   */
    35  var OAuthSimple;
    36  
    37  if (OAuthSimple === undefined)
    38  {
    39      /* Simple OAuth
    40       *
    41       * This class only builds the OAuth elements, it does not do the actual
    42       * transmission or reception of the tokens. It does not validate elements
    43       * of the token. It is for client use only.
    44       *
    45       * api_key is the API key, also known as the OAuth consumer key
    46       * shared_secret is the shared secret (duh).
    47       *
    48       * Both the api_key and shared_secret are generally provided by the site
    49       * offering OAuth services. You need to specify them at object creation
    50       * because nobody <explative>ing uses OAuth without that minimal set of
    51       * signatures.
    52       *
    53       * If you want to use the higher order security that comes from the
    54       * OAuth token (sorry, I don't provide the functions to fetch that because
    55       * sites aren't horribly consistent about how they offer that), you need to
    56       * pass those in either with .setTokensAndSecrets() or as an argument to the
    57       * .sign() or .getHeaderString() functions.
    58       *
    59       * Example:
    60         <code>
    61          var oauthObject = OAuthSimple().sign({path:'http://example.com/rest/',
    62                                                parameters: 'foo=bar&gorp=banana',
    63                                                signatures:{
    64                                                  api_key:'12345abcd',
    65                                                  shared_secret:'xyz-5309'
    66                                               }});
    67          document.getElementById('someLink').href=oauthObject.signed_url;
    68         </code>
    69       *
    70       * that will sign as a "GET" using "SHA1-MAC" the url. If you need more than
    71       * that, read on, McDuff.
    72       */
    73  
    74      /** OAuthSimple creator
    75       *
    76       * Create an instance of OAuthSimple
    77       *
    78       * @param api_key {string}       The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use.
    79       * @param shared_secret (string) The shared secret. This value is also usually provided by the site you wish to use.
    80       */
    81      OAuthSimple = function (consumer_key,shared_secret)
    82      {
    83  /*        if (api_key == undefined)
    84              throw("Missing argument: api_key (oauth_consumer_key) for OAuthSimple. This is usually provided by the hosting site.");
    85          if (shared_secret == undefined)
    86              throw("Missing argument: shared_secret (shared secret) for OAuthSimple. This is usually provided by the hosting site.");
    87  */      this._secrets={};
    88          this._parameters={};
    89  
    90          // General configuration options.
    91          if (consumer_key !== undefined) {
    92              this._secrets['consumer_key'] = consumer_key;
    93              }
    94          if (shared_secret !== undefined) {
    95              this._secrets['shared_secret'] = shared_secret;
    96              }
    97          this._default_signature_method= "HMAC-SHA1";
    98          this._action = "GET";
    99          this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
   100  
   101  
   102          this.reset = function() {
   103              this._parameters={};
   104              this._path=undefined;
   105              return this;
   106          };
   107  
   108          /** set the parameters either from a hash or a string
   109           *
   110           * @param {string,object} List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash)
   111           */
   112          this.setParameters = function (parameters) {
   113              if (parameters === undefined) {
   114                  parameters = {};
   115                  }
   116              if (typeof(parameters) == 'string') {
   117                  parameters=this._parseParameterString(parameters);
   118                  }
   119              this._parameters = parameters;
   120              if (this._parameters['oauth_nonce'] === undefined) {
   121                  this._getNonce();
   122                  }
   123              if (this._parameters['oauth_timestamp'] === undefined) {
   124                  this._getTimestamp();
   125                  }
   126              if (this._parameters['oauth_method'] === undefined) {
   127                  this.setSignatureMethod();
   128                  }
   129              if (this._parameters['oauth_consumer_key'] === undefined) {
   130                  this._getApiKey();
   131                  }
   132              if(this._parameters['oauth_token'] === undefined) {
   133                  this._getAccessToken();
   134                  }
   135  
   136              return this;
   137          };
   138  
   139          /** convienence method for setParameters
   140           *
   141           * @param parameters {string,object} See .setParameters
   142           */
   143          this.setQueryString = function (parameters) {
   144              return this.setParameters(parameters);
   145          };
   146  
   147          /** Set the target URL (does not include the parameters)
   148           *
   149           * @param path {string} the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
   150           */
   151          this.setURL = function (path) {
   152              if (path == '') {
   153                  throw ('No path specified for OAuthSimple.setURL');
   154                  }
   155              this._path = path;
   156              return this;
   157          };
   158  
   159          /** convienence method for setURL
   160           *
   161           * @param path {string} see .setURL
   162           */
   163          this.setPath = function(path){
   164              return this.setURL(path);
   165          };
   166  
   167          /** set the "action" for the url, (e.g. GET,POST, DELETE, etc.)
   168           *
   169           * @param action {string} HTTP Action word.
   170           */
   171          this.setAction = function(action) {
   172              if (action === undefined) {
   173                  action="GET";
   174                  }
   175              action = action.toUpperCase();
   176              if (action.match('[^A-Z]')) {
   177                  throw ('Invalid action specified for OAuthSimple.setAction');
   178                  }
   179              this._action = action;
   180              return this;
   181          };
   182  
   183          /** set the signatures (as well as validate the ones you have)
   184           *
   185           * @param signatures {object} object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:}
   186           */
   187          this.setTokensAndSecrets = function(signatures) {
   188              if (signatures)
   189              {
   190                  for (var i in signatures) {
   191                      this._secrets[i] = signatures[i];
   192                      }
   193              }
   194              // Aliases
   195              if (this._secrets['api_key']) {
   196                  this._secrets.consumer_key = this._secrets.api_key;
   197                  }
   198              if (this._secrets['access_token']) {
   199                  this._secrets.oauth_token = this._secrets.access_token;
   200                  }
   201              if (this._secrets['access_secret']) {
   202                  this._secrets.oauth_secret = this._secrets.access_secret;
   203                  }
   204              // Gauntlet
   205              if (this._secrets.consumer_key === undefined) {
   206                  throw('Missing required consumer_key in OAuthSimple.setTokensAndSecrets');
   207                  }
   208              if (this._secrets.shared_secret === undefined) {
   209                  throw('Missing required shared_secret in OAuthSimple.setTokensAndSecrets');
   210                  }
   211              if ((this._secrets.oauth_token !== undefined) && (this._secrets.oauth_secret === undefined)) {
   212                  throw('Missing oauth_secret for supplied oauth_token in OAuthSimple.setTokensAndSecrets');
   213                  }
   214              return this;
   215          };
   216  
   217          /** set the signature method (currently only Plaintext or SHA-MAC1)
   218           *
   219           * @param method {string} Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now)
   220           */
   221          this.setSignatureMethod = function(method) {
   222              if (method === undefined) {
   223                  method = this._default_signature_method;
   224                  }
   225              //TODO: accept things other than PlainText or SHA-MAC1
   226              if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) === undefined) {
   227                  throw ('Unknown signing method specified for OAuthSimple.setSignatureMethod');
   228                  }
   229              this._parameters['oauth_signature_method']= method.toUpperCase();
   230              return this;
   231          };
   232  
   233          /** sign the request
   234           *
   235           * note: all arguments are optional, provided you've set them using the
   236           * other helper functions.
   237           *
   238           * @param args {object} hash of arguments for the call
   239           *                   {action:, path:, parameters:, method:, signatures:}
   240           *                   all arguments are optional.
   241           */
   242          this.sign = function (args) {
   243              if (args === undefined) {
   244                  args = {};
   245                  }
   246              // Set any given parameters
   247              if(args['action'] !== undefined) {
   248                  this.setAction(args['action']);
   249                  }
   250              if (args['path'] !== undefined) {
   251                  this.setPath(args['path']);
   252                  }
   253              if (args['method'] !== undefined) {
   254                  this.setSignatureMethod(args['method']);
   255                  }
   256              this.setTokensAndSecrets(args['signatures']);
   257              if (args['parameters'] !== undefined){
   258              this.setParameters(args['parameters']);
   259              }
   260              // check the parameters
   261              var normParams = this._normalizedParameters();
   262              this._parameters['oauth_signature']=this._generateSignature(normParams);
   263              return {
   264                  parameters: this._parameters,
   265                  signature: this._oauthEscape(this._parameters['oauth_signature']),
   266                  signed_url: this._path + '?' + this._normalizedParameters(),
   267                  header: this.getHeaderString()
   268              };
   269          };
   270  
   271          /** Return a formatted "header" string
   272           *
   273           * NOTE: This doesn't set the "Authorization: " prefix, which is required.
   274           * I don't set it because various set header functions prefer different
   275           * ways to do that.
   276           *
   277           * @param args {object} see .sign
   278           */
   279          this.getHeaderString = function(args) {
   280              if (this._parameters['oauth_signature'] === undefined) {
   281                  this.sign(args);
   282                  }
   283  
   284              var result = 'OAuth ';
   285              for (var pName in this._parameters)
   286              {
   287                  if (!pName.match(/^oauth/)) {
   288                      continue;
   289                      }
   290                  if ((this._parameters[pName]) instanceof Array)
   291                  {
   292                      var pLength = this._parameters[pName].length;
   293                      for (var j=0;j<pLength;j++)
   294                      {
   295                          result += pName +'="'+this._oauthEscape(this._parameters[pName][j])+'" ';
   296                      }
   297                  }
   298                  else
   299                  {
   300                      result += pName + '="'+this._oauthEscape(this._parameters[pName])+'" ';
   301                  }
   302              }
   303              return result;
   304          };
   305  
   306          // Start Private Methods.
   307  
   308          /** convert the parameter string into a hash of objects.
   309           *
   310           */
   311          this._parseParameterString = function(paramString){
   312              var elements = paramString.split('&');
   313              var result={};
   314              for(var element=elements.shift();element;element=elements.shift())
   315              {
   316                  var keyToken=element.split('=');
   317                  var value='';
   318                  if (keyToken[1]) {
   319                      value=decodeURIComponent(keyToken[1]);
   320                      }
   321                  if(result[keyToken[0]]){
   322                      if (!(result[keyToken[0]] instanceof Array))
   323                      {
   324                          result[keyToken[0]] = Array(result[keyToken[0]],value);
   325                      }
   326                      else
   327                      {
   328                          result[keyToken[0]].push(value);
   329                      }
   330                  }
   331                  else
   332                  {
   333                      result[keyToken[0]]=value;
   334                  }
   335              }
   336              return result;
   337          };
   338  
   339          this._oauthEscape = function(string) {
   340              if (string === undefined) {
   341                  return "";
   342                  }
   343              if (string instanceof Array)
   344              {
   345                  throw('Array passed to _oauthEscape');
   346              }
   347              return encodeURIComponent(string).replace(/\!/g, "%21").
   348              replace(/\*/g, "%2A").
   349              replace(/'/g, "%27").
   350              replace(/\(/g, "%28").
   351              replace(/\)/g, "%29");
   352          };
   353  
   354          this._getNonce = function (length) {
   355              if (length === undefined) {
   356                  length=5;
   357                  }
   358              var result = "";
   359              var cLength = this._nonce_chars.length;
   360              for (var i = 0; i < length;i++) {
   361                  var rnum = Math.floor(Math.random() *cLength);
   362                  result += this._nonce_chars.substring(rnum,rnum+1);
   363              }
   364              this._parameters['oauth_nonce']=result;
   365              return result;
   366          };
   367  
   368          this._getApiKey = function() {
   369              if (this._secrets.consumer_key === undefined) {
   370                  throw('No consumer_key set for OAuthSimple.');
   371                  }
   372              this._parameters['oauth_consumer_key']=this._secrets.consumer_key;
   373              return this._parameters.oauth_consumer_key;
   374          };
   375  
   376          this._getAccessToken = function() {
   377              if (this._secrets['oauth_secret'] === undefined) {
   378                  return '';
   379                  }
   380              if (this._secrets['oauth_token'] === undefined) {
   381                  throw('No oauth_token (access_token) set for OAuthSimple.');
   382                  }
   383              this._parameters['oauth_token'] = this._secrets.oauth_token;
   384              return this._parameters.oauth_token;
   385          };
   386  
   387          this._getTimestamp = function() {
   388              var d = new Date();
   389              var ts = Math.floor(d.getTime()/1000);
   390              this._parameters['oauth_timestamp'] = ts;
   391              return ts;
   392          };
   393  
   394          this.b64_hmac_sha1 = function(k,d,_p,_z){
   395          // heavily optimized and compressed version of http://pajhome.org.uk/crypt/md5/sha1.js
   396          // _p = b64pad, _z = character size; not used here but I left them available just in case
   397          if(!_p){_p='=';}if(!_z){_z=8;}function _f(t,b,c,d){if(t<20){return(b&c)|((~b)&d);}if(t<40){return b^c^d;}if(t<60){return(b&c)|(b&d)|(c&d);}return b^c^d;}function _k(t){return(t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;}function _s(x,y){var l=(x&0xFFFF)+(y&0xFFFF),m=(x>>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<<c)|(n>>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i<x.length;i+=16){var o=a,p=b,q=c,r=d,s=e;for(var j=0;j<80;j++){if(j<16){w[j]=x[i+j];}else{w[j]=_r(w[j-3]^w[j-8]^w[j-14]^w[j-16],1);}var t=_s(_s(_r(a,5),_f(j,b,c,d)),_s(_s(e,w[j]),_k(j)));e=d;d=c;c=_r(b,30);b=a;a=t;}a=_s(a,o);b=_s(b,p);c=_s(c,q);d=_s(d,r);e=_s(e,s);}return[a,b,c,d,e];}function _b(s){var b=[],m=(1<<_z)-1;for(var i=0;i<s.length*_z;i+=_z){b[i>>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i<b.length*4;i+=3){var r=(((b[i>>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
   398          }
   399  
   400  
   401          this._normalizedParameters = function() {
   402              var elements = new Array();
   403              var paramNames = [];
   404              var ra =0;
   405              for (var paramName in this._parameters)
   406              {
   407                  if (ra++ > 1000) {
   408                      throw('runaway 1');
   409                      }
   410                  paramNames.unshift(paramName);
   411              }
   412              paramNames = paramNames.sort();
   413              pLen = paramNames.length;
   414              for (var i=0;i<pLen; i++)
   415              {
   416                  paramName=paramNames[i];
   417                  //skip secrets.
   418                  if (paramName.match(/\w+_secret/)) {
   419                      continue;
   420                      }
   421                  if (this._parameters[paramName] instanceof Array)
   422                  {
   423                      var sorted = this._parameters[paramName].sort();
   424                      var spLen = sorted.length;
   425                      for (var j = 0;j<spLen;j++){
   426                          if (ra++ > 1000) {
   427                              throw('runaway 1');
   428                              }
   429                          elements.push(this._oauthEscape(paramName) + '=' +
   430                                    this._oauthEscape(sorted[j]));
   431                      }
   432                      continue;
   433                  }
   434                  elements.push(this._oauthEscape(paramName) + '=' +
   435                                this._oauthEscape(this._parameters[paramName]));
   436              }
   437              return elements.join('&');
   438          };
   439  
   440          this._generateSignature = function() {
   441  
   442              var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+
   443                  this._oauthEscape(this._secrets.oauth_secret);
   444              if (this._parameters['oauth_signature_method'] == 'PLAINTEXT')
   445              {
   446                  return secretKey;
   447              }
   448              if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1')
   449              {
   450                  var sigString = this._oauthEscape(this._action)+'&'+this._oauthEscape(this._path)+'&'+this._oauthEscape(this._normalizedParameters());
   451                  return this.b64_hmac_sha1(secretKey,sigString);
   452              }
   453              return null;
   454          };
   455  
   456      return this;
   457      };
   458  }