Happy Business Starts Here

Zuora Staff

See updates in Red [06/03/16]

 

What is this change?  

At Zuora, our customers trust is our #1 value, and we take the protection of our customers' data very seriously. To maintain the highest security standards and promote the protection of your data, we occasionally need to make security improvements and deprecate older encryption protocols. To maintain alignment with industry standard best practices and comply with PCI DSS requirements, Zuora will disable the use of TLS 1.0 for inbound connections to Zuora as well outbound callouts from Zuora.

 

What will this change affect? 

This change will affect all incoming web browser based traffic as well as API traffic to both API Sandbox and Production. 

 

When will this change take place? 

We will take a phased approached to disabling TLS 1.0 for both inbound and outbound API calls to allow customers ample time to test and ensure your preparation.

 

Phase 1 - APISandbox and Services Environment

On February 4th 2016 from 7 AM PST - 11 AM PST, we will enforce TLS 1.1 or higher protocols only and disable TLS 1.0 connections for API Sandbox. 

 

From March 17th-31st 2016 7AM PST - 11AM PST, we will enforce TLS 1.1 or higher protocols only and disable TLS 1.0 connections for Services. For the specific implementation date for your service tenant, please submit a ticket through our Support Center.

 

Services Deployment Schedule

3/17/2016 Services environments with suffix ranging from 101-266

3/24/2016 Services environments with suffix ranging from 276-385

3/31/2016 All other Services environments

 

Phase 2 - Production [UPDATED 06/03/16]

Zuora will disable TLS 1.0 for all inbound calls to production on October 13th, 2016. This change will impact all channels including SOAP APIs, REST APIs and browser based traffic (UI).
 

How do I prepare for this change?  

We have split up the preparation section to cover inbound calls to Zuora for browser based traffic as well as API based traffic. 

Testing should be done prior to Feb 4th 2016 when we make the change in API Sandbox. 

 

Inbound Preparation (API and Web Browsing) 

For Inbound API testing, using the following endpoints listed below based on your need to test SOAP or REST API’s.

TLS1 endpoints below have been decomissioned as of 2/4/16

See the table below for common libraries and their compatibility with TLS 1.1 or higher. If the library you use is not listed here, please reach out to your software vendor for more information regarding support for TLS 1.1 or higher. 

 

Library       

TLS 1.1/1.2 Compatibility Notes

Java 8 (1.8) and higher

Compatible by default

Java 7 (1.7)

See Java documentation to enable TLS 1.1 and TLS 1.2

Java 6 (1.6) and below

Not compatible with TLS 1.1 or higher encryption

.NET 4.5 and higher

Compatible by default

.NET 4.0

TLS 1.2 not enabled by default. To enable TLS 1.2, it is possible to set the SchUseStrongCrypto DWORD value in the following two registry keys to 1, creating them if they don't exist: "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" and "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319".

.NET 3.5 and below

Not compatible with TLS 1.1 or higher encryption

Python 2.7.9 and higher

Compatible by default

Python 2.7.8 and below

Not compatible with TLS 1.1 or higher encryption

Ruby 2.0.0

TLS 1.2 is enabled by default when used with OpenSSL 1.0.1 or higher. Using the :TLSv1_2 (preferred) or :TLSv1_1 symbols with an SSLContext's ssl_version ensures TLS 1.0 or earlier is disabled

Ruby 1.9.3 and below

The :TLSv1_2 symbol does not exist in 1.9.3 and below. It can be patch to add that symbol and compile Ruby with OpenSSL 1.0.1 or higher

Windows Server 2008 R2 and higher

Compatible by default

Windows Server 2008 and below

Not compatible with TLS 1.1 or higher encryption

OpenSSL 1.0.1 and higher

Compatible by default

OpenSSL 1.0.0 and below

Not compatible with TLS 1.1 or higher encryption

Mozilla NSS 3.15.1 and higher

Compatible by default

Mozilla NSS 3.14 to 3.15

Compatible with TLS 1.1, but not with TLS 1.2

Mozilla NS 3.13.6 and below

Not compatible with TLS 1.1 or higher encryption

 

Inbound Browser Preparation


To test web browsing, first ensure your browser meets Zuora’s Browser Support Policy found here

Once you have confirmed you are using a supported browser and version, surf to  https://tls1.apisandbox.zuora.com/apps/newlogin.do and login to confirm you can access the environment. Below is table listing the version of supported browsers and their support for TLS 1.1 or higher.


Browser

Compatibility

Desktop and mobile IE version 11

Compatible by default

Desktop IE versions 9 and 10

Capable when run in Windows 7 or newer, but not by default

Microsoft Edge

Compatible by default

Firefox 27 and higher

Compatible by default

Google Chrome 38 and higher

Compatible by default

Mobile Safari versions 5 and higher

Compatible by default


Outbound Preparation (API) 

Integrations using Java will need to use Java 8 which supports TLS 1.1/1.2 by default. See here for more details.   

Integrations that run on Windows will need to run on Windows Server 2008 R2 or higher. This generally includes most .NET applications and Microsoft Internet Information Server (IIS). Earlier versions of Windows Server do not support TLS 1.1 or TLS 1.2. See here for details. 

Integrations which rely on OpenSSL should ensure they are using OpenSSL version 1.01 or newer. See here for changelogs.

 

What happens if I take no action? 

Customers are advised to immediately perform the necessary changes  to ensure support for protocol versions  TLS 1.1 or higher.. If you have made the necessary changes, no further action is required on your part.

 

Failure to make the necessary changes before October, 13th, 2016 to support TLS 1.1 or higher will result in a disruption of Zuora services for your integration.


Zuora Global Support is readily available to answer any additional questions you may have.   

Please contact us at +1-650-779-4993 or at support@zuora.com.  

 

 

52 Comments
Master

I'm doing fine in python, using a module named zeep, and python 3.5.2.

 

Here's my code:

 

#!/usr/local/bin/python3

import pdb
import config
import json
import datetime
import zeep
import sys


# Config file format:
#     {
#       "user":     "zuora_api_user@canonical.com.apisandbox",
#       "password": "xxxxxxx",
#       "wsdl":     "/Users/myself/.zuora/zuora.a.81.0.wsdl",
#       "//endpoint": "https://apisandbox.zuora.com/apps/services/a/78.0",
#       "verboseLog": false
#     }


class ZuoraSoap():
    def __init__(self, config):
        with open(config) as configfile:
            self.config = json.load(configfile)

            self.client = zeep.Client(wsdl=self.config['wsdl'])
            response = self.client.service.login(self.config['user'], self.config['password'])
            sessionid = response.Session

            sessionheader_cls = self.client.get_element('ns1:SessionHeader')
            self.sessionheader = sessionheader_cls(session=sessionid)
            
            self.batchSize = 2000
            self.caseSensitive = False
        
    def query(self, query):
        queryoptions_cls = self.elementFactory('QueryOptions')
        queryoptions = queryoptions_cls(batchSize=self.batchSize, caseSensitive=self.caseSensitive)
        
        query_response = self.client.service.query(
                        queryString=query, 
                        _soapheaders={'QueryOptions': queryoptions, 'SessionHeader': self.sessionheader})
        return query_response
        
    def typeFactory(self, type, ns='ns0'):
        return self.client.get_type(ns + ':' + type)
        
    def elementFactory(self, element, ns='ns1'):
        return self.client.get_element(ns + ':' + element)
        
    # wsdl:
    # create(zObjects: zObject[], _soapheaders={CallOptions: CallOptions(), SessionHeader: SessionHeader()})
    # -> Errors: Error[], Id: xsd:ID, Success: xsd:boolean
    
    def create(self, zObjectList):
        calloptions_cls = self.elementFactory('CallOptions')
        calloptions = calloptions_cls(useSingleTransaction=False)
        response = self.client.service.create(
            zObjects=zObjectList,
            _soapheaders={'CallOptions': calloptions, 'SessionHeader': self.sessionheader})
        return response
        
    # wsdl:
    # delete(type: xsd:string, ids: xsd:ID[], _soapheaders={SessionHeader: SessionHeader()}) 
    # -> errors: Error[], id: xsd:ID, success: xsd:boolean
    #
    # 50 objects are supported in a single call.
    # 1,000 calls are supported per 10-minute time window per tenant

    def delete(self, type, ids):
        assert len(ids) <= 50
        response = self.client.service.delete(
                type=type,
                ids=ids,
                _soapheaders={'SessionHeader': self.sessionheader})
        return response


    def updateRecords(self, zObjects):
        saveResults = []
        for chunk in [zObjects[i:i + config.ZUORA_CHUNKSIZE] for i in range(0, len(zObjects), config.ZUORA_CHUNKSIZE)]:
            saveResults += self.client.service.update(
                        zObjects=chunk,
                        _soapheaders={'SessionHeader': self.sessionheader})
        return saveResults        
        
    # generate(zObjects: zObject[], _soapheaders={SessionHeader: SessionHeader()}) -> Errors: Error[], Id: xsd:ID, Success: xsd:boolean
    # https://knowledgecenter.zuora.com/DC_Developers/SOAP_API/E_SOAP_API_Calls/generate_call
    def generate(self, invoices): 
        return self.client.service.generate(
                    zObjects=invoices,
                    _soapheaders={'SessionHeader': self.sessionheader})

 

 

Master

Here's a bit of code use in migration; it generates and posts an invoice for the day before the current fiscal year, then another for the first of each month, for all accounts.

 

#!/usr/local/bin/python3

import logging
import datetime
from dateutil.relativedelta import *
import pdb
from progressbar import ProgressBar

import config
from zuorasoap import ZuoraSoap



def getZuoraAccounts(zuora):
    accountsByAccountName = {}
    result = zuora.query("select Id, Name, AccountNumber, Status from Account Where Status = 'Active'" )
    if result.size > 0:
        for record in result.records:
            accountsByAccountName[record['Name']] = record

    return accountsByAccountName


def getInvoiceDates():
    invoiceDates = [config.INITIAL_INVOICE_DATE]
    nextInvoiceDate = config.INITIAL_INVOICE_DATE+datetime.timedelta(days=1)
    while nextInvoiceDate < datetime.date.today():
        invoiceDates.append(nextInvoiceDate)
        nextInvoiceDate += relativedelta(months=+1)
    return invoiceDates
    
def main():
    logger = logging.getLogger("migration")
    logger.setLevel(logging.INFO)
    fh = logging.FileHandler("migration.log")
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    logger.addHandler(fh)

    logger.info("Runbills started")


    invoiceDates = getInvoiceDates()
    
                      
    zuora = ZuoraSoap(config.ZUORA_CONFIGFILE)
    accountsByAccountName = getZuoraAccounts(zuora)

    invoiceFactory = zuora.typeFactory('Invoice')




    progressbar = ProgressBar()
    for accountName in progressbar(accountsByAccountName):
        account = accountsByAccountName[accountName]
    

        for invoiceDate in invoiceDates:
            invoiceRecord=invoiceFactory(
                                AccountId=account['Id']
                                ,InvoiceDate=invoiceDate
                                ,TargetDate=invoiceDate
                                ,IncludesOneTime=True
                                ,IncludesRecurring=True
                                ,IncludesUsage=True)
            results = zuora.generate([invoiceRecord])
            for result in results:
                if not result['Success']:
                    if len(result['Errors']) == 1 and 'no charges due' in result['Errors'][0]['Message']:
                        pass
                    else:
                        pdb.set_trace()
                        logger.error(invoiceRecord)
                        logger.error(result)
                else:
                    invoiceId = result['Id']
                    results = zuora.updateRecords([invoiceFactory(Id=invoiceId,Status='Posted')])
                    for result in results:
                        if not result['Success']:
                            logger.error(result)
                        
    logger.info("Runbills exited")


if __name__ == "__main__":
    main()
    pass