Search code examples
pythonauthenticationgoogle-apps-scriptnetsuitehmacsha256

Using Google Apps Script to Update NetSuite


Trying to convert a Python script that works to Google Apps Script as easier to distribute. Appears to function as it should and generates a signature however with using the same nonce and timestamp from the Python script I can see that Google Apps script creates a different Signature and I can't for the life of me fathom out why. I know it will fail I'm just trying to match up the signatures before continuing!

Any ideas on any methods that will work from Google Apps Script for connecting to NetSuite using TBA - I repeat this needs to use TBA not OAuth2 etc. Any help or guidance appreciated.

 function main() {
  var REALM = 'XXXXX';
  var CONSUMER_KEY = 'XXXXXXx';
  var CONSUMER_SECRET = 'XXXXXXXX';
  var RESOURCE_OWNER_KEY = 'XXXXXXXXX';
  var RESOURCE_OWNER_SECRET = 'XXXXXXXX';
  
  var method = "PATCH";
  var url = 'https://' + REALM + '.suitetalk.api.netsuite.com/services/rest/record/v1/vendor/1846';
  
  var params = {
    'oauth_consumer_key': CONSUMER_KEY,
    'oauth_nonce': '38751934124598253091697458273',
    'oauth_signature_method': 'HMAC-SHA256',
    'oauth_timestamp': '1697458273',
    'oauth_token': RESOURCE_OWNER_KEY,
    'oauth_version': '1.0',
    'realm': REALM
  };
  
  // Sorting and encoding the parameters
  // Sorting and encoding the parameters
  var paramKeys = Object.keys(params);
  paramKeys.sort();
  var paramStr = paramKeys.map(function(key) {
  return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
  }).join('&');
  
  // Create base string
  var baseString = method + '&' + encodeURIComponent(url) + '&' + encodeURIComponent(paramStr);
  
  // Create signature key
  var signatureKey = encodeURIComponent(CONSUMER_SECRET) + '&' + encodeURIComponent(RESOURCE_OWNER_SECRET);
  
  // Generate HMAC-SHA256 signature
  var signature = Utilities.computeHmacSha256Signature(baseString, signatureKey);
  signature = Utilities.base64Encode(signature);
  
  Logger.log('Base String: ' + baseString);
  Logger.log('Signature Key: ' + signatureKey);
  Logger.log('Signature: ' + signature);
  
  // Prepare OAuth1.0 headers
  params['oauth_signature'] = signature;
  var authHeader = 'OAuth ' + Object.keys(params).map(function(key) {
    return encodeURIComponent(key) + '="' + encodeURIComponent(params[key]) + '"';
  }).join(', ');
  Logger.log("Params are:   "+JSON.stringify(params))
  
  // Prepare HTTP options
  var options = {
    'method': method,
    'headers': {
      'Authorization': authHeader,
      'Content-Type': 'application/json'
    },
    'payload': JSON.stringify({"custentity_id": "2"})
  };
  
  // Send HTTP request and log the response
  try {
    var response = UrlFetchApp.fetch(url, options);
    Logger.log('Response: ' + response.getContentText());
  } catch (e) {
    Logger.log('Error: ' + e);
  }
}

Python Script

import requests
import oauthlib.oauth1
import json
import hmac
import hashlib
import base64
from urllib.parse import quote_plus
from pprint import pprint

# Constants and configurations
REALM = 'XXXXXXX'
CONSUMER_KEY = 'XXXXXXXXXXX'
CONSUMER_SECRET = 'XXXXXXXX'
RESOURCE_OWNER_KEY = 'XXXXXXXX'
RESOURCE_OWNER_SECRET = 'XXXXXXXX'

# Create a session object
session = requests.Session()


def generate_signature(base_string, signature_key):
    hmac_obj = hmac.new(signature_key.encode('utf-8'), base_string.encode('utf-8'), hashlib.sha256)
    return base64.b64encode(hmac_obj.digest()).decode('utf-8')


def create_netsuite_client():
    client = oauthlib.oauth1.Client(
        CONSUMER_KEY,
        client_secret=CONSUMER_SECRET,
        resource_owner_key=RESOURCE_OWNER_KEY,
        resource_owner_secret=RESOURCE_OWNER_SECRET,
        realm=REALM,
        signature_method="HMAC-SHA256",
        timestamp='1697458273',
        nonce='38751934124598253091697458273'
    )
    return client


def process_vendor(netsuite, id, netsuite_id):
    data = {"custentity_id": id}
    hdr = {"Content-Type": "application/json"}
    url = f'https://{REALM}.suitetalk.api.netsuite.com/services/rest/record/v1/vendor/{netsuite_id}'
    url, headers, body = netsuite.sign(url, headers=hdr, body=json.dumps(data), http_method="PATCH")
    pprint(headers)
    try:
        response = session.request("PATCH", url, headers=headers, data=body)
        response.raise_for_status()
        print(f"{id},{netsuite_id},Successful")
    except requests.RequestException as err:
        print(f"{id},{netsuite_id},{str(err)},{json.dumps(response.json()).replace(',', ' ')}")


def main():
    netsuite = create_netsuite_client()
    process_vendor(netsuite, '2', '1846')


if __name__ == '__main__':
    main()


Solution

  • Just incase it helps anyone else - went back to basics and managed to rectify - combination of things but mainly the ordering of the OAuth params and headers. All values are dummy ones!! Probably not the most elegant but the end result has been achieved!!

    function upsertOperation(externalId, body) {
      // Constants and configurations
      var REALM = '123456';
      var CONSUMER_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
      var CONSUMER_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
      var RESOURCE_OWNER_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
      var RESOURCE_OWNER_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx';
      var NETSUITE_ID = '1234';
      var ID = '2';
      var METHOD = "PATCH";
      var baseUrl = 'https://123456.suitetalk.api.netsuite.com/services/rest/record/v1/vendor/' + NETSUITE_ID;
    
      // Generate OAuth nonce and timestamp
      var oauthNonce = Utilities.getUuid();
      var oauthTimestamp = Math.floor(new Date().getTime() / 1000);
    
      // OAuth Parameters
      var oauthSignatureMethod = 'HMAC-SHA256';
      var oauthVersion = '1.0';
      var realm = REALM;
    
      // Collect and sort OAuth parameters
      var oauthParameters = {
        oauth_consumer_key: CONSUMER_KEY,
        oauth_token: RESOURCE_OWNER_KEY,
        oauth_nonce: oauthNonce,
        oauth_timestamp: oauthTimestamp,
        oauth_signature_method: oauthSignatureMethod,
        oauth_version: oauthVersion
      };
    
      var sortedParameters = Object.keys(oauthParameters)
        .sort()
        .map(function(key) {
          return key + '=' + oauthParameters[key];
        })
        .join('&');
      
      Logger.log("Sorted Params: "+sortedParameters);
    
    
    
    
      // Compute OAuth signature
      var signatureBaseString = METHOD + '&' + encodeURIComponent(baseUrl) + '&' + encodeURIComponent(sortedParameters);  //WORKING CORRECTLY
      Logger.log("Signature BaseString: "+signatureBaseString);
    
      var signingKey = encodeURIComponent(CONSUMER_SECRET) + '&' + encodeURIComponent(RESOURCE_OWNER_SECRET);    //WORKING CORRECTLY
      Logger.log("Signing Key: "+signingKey);
    
      var hmac = Utilities.computeHmacSha256Signature(signatureBaseString, signingKey);
      var oauthSignature = Utilities.base64Encode(hmac);
      Logger.log("SIGNATURE: "+ oauthSignature);
    
      // Prepare headers
    var headers = {
      'Content-Type': 'application/json',
      'Authorization': 'OAuth ' +
        'realm="' + encodeURIComponent(realm) + '", ' +
        'oauth_token="' + encodeURIComponent(RESOURCE_OWNER_KEY) + '", ' +
        'oauth_consumer_key="' + encodeURIComponent(CONSUMER_KEY) + '", ' +
        'oauth_nonce="' + encodeURIComponent(oauthNonce) + '", ' +
        'oauth_timestamp="' + encodeURIComponent(oauthTimestamp) + '", ' +
        'oauth_signature_method="' + encodeURIComponent(oauthSignatureMethod) + '", ' +
        'oauth_version="' + encodeURIComponent(oauthVersion) + '", ' +
        'oauth_signature="' + encodeURIComponent(oauthSignature) + '"'
    };
      Logger.log("Headers: " + JSON.stringify(headers, null, 2));
    
      var PAYLOAD = {
        'id': id
      };
    
      // HTTP request options
      var options = {
        method: METHOD,
        headers: headers,
        payload: JSON.stringify(PAYLOAD),
        followRedirects: true
      };
    
      // Perform HTTP request and log response
      var response = UrlFetchApp.fetch(baseUrl, options);
      
      if (response.getResponseCode() === 200) {
        // Successfully connected
        var json = response.getContentText();
        var data = JSON.parse(json);
        // Do something with the data
      } else {
        // Log the error information for debugging
        Logger.log("Error: " + response.getContentText());
      }
    }