Accessing QBO via Automation

Background

To access QBO via automation, developers can leverage QBO 3's mixed authentication. This allows the developer's software to access QBO functionality without the need to explicitly call Security/Login.ashx. The mixed authentication works as follows:
  • If there is no User-Agent HTTP header, basic authentication will be used
  • If there is an HTTP header matching any one of the headers specified in qbo.Security.Properties.Settings.BasicAuthenticationHeaders, basic authentication will be used
  • If the HTTP User-Agent header contains any of the user agents strings specified in qbo.Security.Properties.Settings.BasicAuthenticationUserAgents, basic authentication will be used
  • Otherwise, forms authentication will be used
By default, the configuration settings are set to:
  • BasicAuthenticationHeaders: SOAPAction
  • BasicAuthenticationUserAgents: Gecko/ /21.0
These may be extended via the UI from Design > Configuration > Modules > Person > Settings. Note that multiple headers and user agents may be specified with a semi-colon delimited list:
  • BasicAuthenticationHeaders: SOAPAction;MyCustomHeader
  • BasicAuthenticationUserAgents: Gecko/ /21.0;curl

Sample Code

See the attached sample C# project.  The key code is below.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace qbo.Sample
{
class Program
{
static void Main(string[] args)
{
// Prompt the user for the {Folder}/{ClassName}/{Operation}?{Parameters} to invoke
Console.Write("Enter Module (e.g. Application or Decision): ");
string module = Console.ReadLine();
Console.Write("Enter Class (e.g. Matrix, SmartWorklist, Attachment): ");
string className = Console.ReadLine();
Console.Write("Enter Method (e.g. Summary, Generate, Lookup): ");
string method = Console.ReadLine();
Console.Write("Parameters (e.g. ID=1&OtherParam=ABC): ");
string parameters = Console.ReadLine();

// Construct the URL to call; the root website URL is stored in app.config
string url = string.Format("{0}{1}/{2}.ashx/{3}?{4}&Output={5}", Properties.Settings.Default.BaseUrl, module, className, method, parameters, Properties.Settings.Default.Output);

// Figure out where to store the response we get back; OutputPath is defined in app.config
string outputFile = string.Format("{0}/{1}.{2}", Properties.Settings.Default.OutputPath, Guid.NewGuid(), Properties.Settings.Default.Output);

WebRequest request = WebRequest.Create(url);

// Establish the user name and password; since we're using a WebRequest, there will be no user-agent header, so QBO 3 will issue a Basic Auth challenge (HTTP 401)
CredentialCache cache = new CredentialCache();

// The sample username and password are stored in app.config
cache.Add(new Uri(Properties.Settings.Default.BaseUrl), "Basic", new NetworkCredential(Properties.Settings.Default.Username, Properties.Settings.Default.Password));
request.Credentials = cache;
request.PreAuthenticate = true;

// Make the call
using (WebResponse response = request.GetResponse())
{
using (Stream from = response.GetResponseStream())
{
int BUFFER_SIZE = 4096;
Byte[] buffer = new Byte[BUFFER_SIZE];
int bytesRead = from.Read(buffer, 0, BUFFER_SIZE);

using (FileStream file = new FileStream(outputFile, FileMode.OpenOrCreate))
{
while (bytesRead > 0)
{
file.Write(buffer, 0, bytesRead);
// Console.Write(Encoding.Default.GetString(buffer));
bytesRead = from.Read(buffer, 0, BUFFER_SIZE);
}
}

}
}

// Let the user know what we did
Console.WriteLine(string.Format("\nURL: {0}", url));
Console.WriteLine(string.Format("Output file: {0}", outputFile));
Console.WriteLine("\n\nPress enter to exit");
Console.ReadLine();
}
}
}

Using 'X-ApiKey' Headers

An alternative to using Basic Authentication is to issue a SecurityAccess row mapping to a desired user account, and have the client issue a custom HTTP header:

X-ApiKey: {SecurityAccess.GUID}

Sample test code can be found in qbo.Security.Tests project in the qbo.Core.Tests solution:

[TestMethod]
public void LoginWithApiKey()
{
// Create a SecurityAccess row mapped to the TestUser account (defaults to admin@quandis.com).
SecurityAccessObject access = new SecurityAccessObject(User)
{
SecurityAccess = "ApiKey Test",
GUID = Guid.NewGuid(),
PersonID = User.UserID
};
access.Save();

// Perform a PersonSearch.
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(string.Format("{0}/Security/Person.ashx/Search?DisplaySize=5", Properties.Settings.Default.TestWebsite));
request.Headers.Add("X-ApiKey", access.GUID.ToString());
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "The response code is OK (200)");
Assert.AreNotEqual(HttpStatusCode.Redirect, response.StatusCode, "The response code is not Redirect (301)");
Assert.IsNotNull(response.Headers["Set-Cookie"], "A Set-Cookie header exists");
Assert.IsTrue(response.Headers["Set-Cookie"].Contains("ASP.NET_SessionId"), "The Set-Cookie header includes an ASP.NET_SessionId cookie.");
Assert.IsTrue(response.Headers["Set-Cookie"].Contains("qbo.Security"), "The Set-Cookie header includes qbo.Security cookie.");
}
}

ċ
qbo.Automation.Sample.zip
(6k)
Eric Patrick,
Mar 13, 2014, 8:14 AM
Comments