5

I am new to this area of using SOAP for NetSuite calls. Hence, I might be thinking completely incorrectly on how to solve the problem. Here is what I am trying to solve: - Language: Python+ Zeep - I want to move my application from email pass to Token based authentication.

In Python I am able to generate all the parameters for TokenPassport. Here is where I am confused: I looked up some code on stack and noticed that folks were using client.service.login() method to login. This method takes the passport and not the tokenpassport obj. Is there a separate method that takes tokenpassport obj for login?, Or do I need to generate(hardcode) an XML with the parameters and is this passed in header as data?

Thanks T

tkansara
  • 534
  • 1
  • 4
  • 21

4 Answers4

6

Hope the below code helps someone who is starting out.

base = '&'.join([nsAccountID, consumerKey, token, Nonce, currentTime])
key = '&'.join([consumerSecret, tokenSecret])
digest = hmac.new(str.encode(key), msg=str.encode(base), digestmod=hashlib.sha256).digest()
signature = base64.b64encode(digest).decode()

tokenPassport = client.get_type('ns0:TokenPassport')
PassportSignature = client.get_type('ns0:TokenPassportSignature')
tokenPassportSignature = PassportSignature(signature, "HMAC-SHA256" )
clientPass = tokenPassport(account=nsAccountId, consumerKey = consumerKey, token= token, nonce= Nonce, timestamp=currentTime, signature=tokenPassportSignature)


search = client.get_type('ns5:ItemSearchBasic')
searchCriteriaComplex = client.get_type('ns0:SearchStringField')
searchCriteria = searchCriteriaComplex(searchValue= "Test Display Name - tax", operator="is")
searchItem = search(displayName = searchCriteria)
testRes = client.service.search(searchRecord= searchItem, _soapheaders={"tokenPassport": clientPass})
John Weldon
  • 39,849
  • 11
  • 94
  • 127
tkansara
  • 534
  • 1
  • 4
  • 21
  • Could you explain what is 'secret' and 'thing_to_has'? are they random value? Thanks, – Xb74Dkjb May 15 '19 at 04:11
  • secretToJoin = (consumerSecret, tokenSecret) secret = connectionJoiner.join(secretToJoin) msgToJoin = (nsAccountId, consumerKey, token, nonce, str(currentTime)) connectionJoiner = "&" msgToJoin = connectionJoiner.join(msgToJoin) thing_to_hash = msgToJoin – tkansara May 17 '19 at 13:50
  • I need serious help regarding this can you please guide me because upper code has alot of things which are not understandable by me – Adam Strauss Jan 29 '21 at 06:28
  • Do you have specific question that I can help with? – tkansara Jan 30 '21 at 07:59
  • For some reason - my endpoint for my request is set to the non-client-specific endpoint, which causes an error: "DEBUG:zeep.transports:HTTP Post to https\:\/\/webservices.netsuite.com/services/NetSuitePort_2023_1" Any ideas why? And thank you for your post! – Gerard ONeill Aug 17 '23 at 23:11
  • Never mind - Not sure when this code was written, but currently need to create a new service proxy with the modern URL scheme that includes the account id. I'll create a different answer. – Gerard ONeill Aug 18 '23 at 01:52
1

Instead of did deeper on this, I tried netsuite which is much better.

Xb74Dkjb
  • 982
  • 9
  • 20
1

Here is how I generate the TokenPassport.

    def _generateTimestamp(self):
        return str(int(time()))

    def _generateNonce(self, length=20):
        """Generate pseudorandom number
        """
        return ''.join([str(random.randint(0, 9)) for i in range(length)])

    def _getSignatureMessage(self, nonce, timestamp):
        return '&'.join(
            (
                self._setting['ACCOUNT'],
                self._setting['CONSUMER_KEY'],
                self._setting['TOKEN_ID'],
                nonce,
                timestamp,
            )
        )

    def _getSignatureKey(self):
        return '&'.join((self._setting['CONSUMER_SECRET'], self._setting['TOKEN_SECRET']))

    def _getSignatureValue(self, nonce, timestamp):
        key = self._getSignatureKey()
        message = self._getSignatureMessage(nonce, timestamp)
        hashed = hmac.new(
            key=key.encode('utf-8'),
            msg=message.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        return base64.b64encode(hashed).decode()

    @property
    def tokenPassport(self):
        TokenPassport = self.getDataType("ns0:TokenPassport")
        TokenPassportSignature = self.getDataType("ns0:TokenPassportSignature")

        nonce = self._generateNonce()
        timestamp = self._generateTimestamp()
        tokenPassportSignature = TokenPassportSignature(
            self._getSignatureValue(nonce, timestamp),
            algorithm='HMAC-SHA256'
        )

        return TokenPassport(
            account=self._setting['ACCOUNT'],
            consumerKey=self._setting['CONSUMER_KEY'],
            token=self._setting['TOKEN_ID'],
            nonce=nonce,
            timestamp=timestamp,
            signature=tokenPassportSignature
        )
Bibo W.
  • 41
  • 1
0

Zeep is great - but I'd like an option to specify a namespace or alternative lookup within the client. But this is the current (2023) way to access Netsuite with zeep, with the needed service address modification.

client.service._binding_options["address"] = (nsURL + nsURLPath)

The default "service" within the netsuite WSDL is the old "https://webservices.netsuite.com/..." instead of the required "https://{companyid}.suitetalk.api.netsuite.com/...".

def createNonce(length=20):
    return ''.join([str(random.randint(0, 9)) for i in range(length)])

def createSignature(passport, tokenSecret, consumerSecret):
    signatureKey = f"{urllib.parse.quote_plus(consumerSecret)}&{urllib.parse.quote_plus(tokenSecret)}"
    baseString = f"{passport.account}&{passport.consumerKey}&{passport.token}&{passport.nonce}&{passport.timestamp}"

    return base64.b64encode(hmac.digest(signatureKey.encode('utf-8'), baseString.encode('utf-8'), hashlib.sha256))

--------------------------------------------------------------------------------------
client = zeep.CachingClient(wsdl=netsuiteWSDL)
client.service._binding_options["address"] = (nsURL + nsURLPath)
#service = client.create_service(netsuiteBinding, address = (nsURL + nsURLPath) )

TokenPassport = client.get_type("ns0:TokenPassport");
tokenPassport = TokenPassport(
    account = nsAccount,
    consumerKey = consumerKey,
    token = tokenKey,
    nonce = createNonce(),
    timestamp = str(int(datetime.now().timestamp()))
)

TokenPassportSignature = client.get_type("ns0:TokenPassportSignature")
hashAlgorithm = "HMAC-SHA256"
tokenPassport["signature"] = TokenPassportSignature(createSignature(tokenPassport, tokenSecret, consumerSecret), hashAlgorithm)

RecordType = client.get_type("ns1:RecordType")

RecordRef = client.get_type("ns0:RecordRef")
recordRef = RecordRef(
    internalId = "14764193",
    type = RecordType("customer")
)

print(client.service.get(recordRef, _soapheaders = { "tokenPassport": tokenPassport } ))

There are probably ways to take advantage of auto-binding, but I like clarity. Ironic, since I don't want to have to search the namespaces for types and methods.

Gerard ONeill
  • 3,914
  • 39
  • 25