【问题标题】:QuickBooks Online querying with filter returns 401 everytimeQuickBooks Online 使用过滤器查询每次都返回 401
【发布时间】:2013-02-04 20:30:08
【问题描述】:

我已成功使用 POST 和 Content-Type application/xml 创建对象

我也使用 Content-Type application/x-www-form-urlencoded 和一个空白请求主体成功查询,该请求主体根据我指定的 URI 返回所有对象类型。

我也可以在请求正文中使用类似 PageNum=1&ResultsPerPage=1 的内容,并且我已经弄清楚如何将其合并到签名中,以便获得有效的响应。

但是,无论我如何格式化它,当我尝试使用过滤器时,除了 401 响应之外,我什么也得不到(像 Filter=FAMILYNAME :EQUALS: Doe 这样的基本操作)。我已经阅读了关于如何使用 [RFC3986] 百分比编码对所有参数名称和值进行转义的 OAuth Core 1.0 Revision A 规范。但是我觉得我错过了一个步骤或格式不正确。我在 Intuit 的论坛中搜索正确的格式时发现了不一致的信息。

对此的任何帮助将不胜感激。我已经为此苦苦挣扎了一个星期。

我在尝试使用过滤器时得到的响应是: HTTP 状态 401 - 消息 = 验证 OAuth 的异常;错误代码=003200;状态码=401

----更新----

当我尝试将过滤器与新 IPP 开发人员工具 - IPP API Explorer 一起使用时,我看到了同样的错误。我正在使用 IDS V2 QBO API Explorer。我可以使用该工具来检索所有帖子,并且响应显示了我所有的客户,但是当我尝试使用过滤器时,我得到: 服务器错误 401 - 未经授权:由于凭据无效,访问被拒绝。 您无权使用您提供的凭据查看此目录或页面。

有什么想法吗?如果我从 API Explorer 工具中得到同样的错误,我会认为问题完全是另外一回事。

----最终更新----

我终于在过滤器方面取得了成功,我相信我已经弄清楚了我的问题所在。我一直怀疑我是否能够使用诸如“PageNum=1&ResultsPerPage=1”之类的分页查询来工作,但无法获得诸如“Filter=FAMILYNAME:EQUALS:Doe”之类的查询。我怀疑过滤器格式中的空白存在问题。之前让我无法追踪的原因是我无法让过滤器在 IDS V2 QBO API Explorer 中工作。这让我怀疑还有其他事情发生。我决定完全忽略 API Explorer,并专注于为什么我可以让它以一种方式工作,而不能以另一种方式工作。

我相信我的问题归结为签名中过滤器值的编码不当。这解释了我得到的 401 无效签名错误。

“Filter=Name :EQUALS:Doe”在标准化后变为“Filter=Name%20%3AEQUALS%20%3ADoe”。

应该给出“Filter%3DName%2520%253AEQUALS%2520%253ADoe”的百分比编码。

本质上,您必须对空格和冒号进行“双重”编码,但不能对等号进行编码。我尝试了许多编码排列,但相信我的错误是我不是“双重”编码,或者当我是双重编码时,我包含了“=”符号。无论哪种方式都会破坏您的签名。感谢大家的意见。

【问题讨论】:

  • 看起来很傻,但你试过用 %20 替换空格吗?
  • 我正在使用一个函数为我进行 RFC3986 编码,但我也尝试了手动编码和其他一些编码方法,但无济于事。感谢您的意见。
  • 感谢您创建自己的 OAuth 签名...我去过那里的生活这让我抓狂

标签: quickbooks intuit-partner-platform quickbooks-online


【解决方案1】:

我相信我的问题归结为签名中过滤器值的编码不当。这解释了我得到的 401 无效签名错误。

我使用在线工具引导我完成正确签署 Oauth 请求的步骤。在完成这些步骤时,我意识到我的问题在于规范化请求参数然后对它们进行百分比编码的步骤。我在规范化步骤中包含了过滤器的“=”,这会破坏您的签名。我使用的工具可以在以下位置找到:

http://hueniverse.com/2008/10/beginners-guide-to-oauth-part-iv-signing-requests/

感谢大家的意见。

【讨论】:

  • 很棒的工具 - 使用它我能够发现我针对 API 的 QBOv3 运行的查询没有被正确编码,因此使我的 OAuth 签名无效并导致相同的消息=异常身份验证 OAuth;错误代码=003200;每个请求的 statusCode=401 问题。
【解决方案2】:

您是否在 API Explorer 中收到带有相同请求的 401?

http://ippblog.intuit.com/blog/2013/01/new-ipp-developer-tool-api-explorer.html

另外,您是使用静态基本 URL 还是在运行时检索它?

https://ipp.developer.intuit.com/0010_Intuit_Partner_Platform/0050_Data_Services/0400_QuickBooks_Online/0100_Calling_Data_Services/0010_Getting_the_Base_URL

如果您使用的是静态基本 URL,请尝试切换到运行时基本 URL 以查看是否仍然出现错误。

【讨论】:

  • 感谢您提供指向新 IPP 开发人员工具的链接。这是非常有用的。然而,我使用它得到了类似的结果,尽管我得到的反应略有不同。我用空白的正文请求成功地检索了所有内容。但是,当尝试在正文中输入过滤器时,我得到响应:401 - 未经授权:由于凭据无效,访问被拒绝。您无权使用您提供的凭据查看此目录或页面。
  • 我使用的是静态基本 URL。我将看到切换到运行时基本 URL 并发布我的结果。谢谢。
  • 我仍在考虑使用静态基本 URL。但是我担心我无法让过滤器在 IPP Developer Tool API Explorer 中工作。使用 IDS V2 QBO API Explorer,我可以通过将请求正文留空来检索所有客户。但是,如果我尝试输入像“Filter=FAMILYNAME:EQUALS:Doe”这样的过滤器,我会得到 401 - Unauthorized: Access is denied due to invalid credentials 错误。这与我尝试通过软件的 POST 调用使用过滤器时看到的行为相同。我开始怀疑这里发生了其他事情。有人有什么想法吗?
  • 我们正在查看问题并通知您。
  • 我尝试切换到运行时基本 URL,但过滤器没有任何成功。我仍然遇到请求正文中 PageNum=1&ResultsPerPage=1 有效的问题,但过滤器不起作用。我正在与 Fiddler 一起整理我的请求和响应的完整示例,以便我可以将其发送给我在 Intuit 的联系人。希望他们能提供帮助。
【解决方案3】:

peterl 在这里回答了我的一个问题,也可能回答你的问题。当过滤器应该进入标题时,我一直试图将它们放在正文中。这是 peterl 的代码示例,用于获取特定客户的所有未付发票(未结余额大于 0.00)。

http://pastebin.com/raw.php?i=7VUB6whp

public List<Intuit.Ipp.Data.Qbo.Invoice> GetQboUnpaidInvoices(DataServices dataServices, int startPage, int resultsPerPage,  IdType CustomerId)
{
    StringBuilder requestXML = new StringBuilder();
    StringBuilder responseXML = new StringBuilder();

    var requestBody = String.Format("PageNum={0}&ResultsPerPage={1}&Filter=OpenBalance :GreaterThan: 0.00 :AND: CustomerId :EQUALS: {2}", startPage, resultsPerPage, CustomerId.Value);

    HttpWebRequest httpWebRequest = WebRequest.Create(dataServices.ServiceContext.BaseUrl + "invoices/v2/" + dataServices.ServiceContext.RealmId) as HttpWebRequest;
    httpWebRequest.Method = "POST";
    httpWebRequest.ContentType = "application/x-www-form-urlencoded";
    httpWebRequest.Headers.Add("Authorization", GetDevDefinedOAuthHeader(httpWebRequest, requestBody));
    requestXML.Append(requestBody);
    UTF8Encoding encoding = new UTF8Encoding();
    byte[] content = encoding.GetBytes(requestXML.ToString());
    using (var stream = httpWebRequest.GetRequestStream())
    {
        stream.Write(content, 0, content.Length);
    }
    HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
    using (Stream data = httpWebResponse.GetResponseStream())
    {
        Intuit.Ipp.Data.Qbo.SearchResults searchResults = (Intuit.Ipp.Data.Qbo.SearchResults)dataServices.ServiceContext.Serializer.Deserialize<Intuit.Ipp.Data.Qbo.SearchResults>(new StreamReader(data).ReadToEnd());
        return ((Intuit.Ipp.Data.Qbo.Invoices)searchResults.CdmCollections).Invoice.ToList();
    }

}

protected string GetDevDefinedOAuthHeader(HttpWebRequest webRequest, string requestBody)
{

    OAuthConsumerContext consumerContext = new OAuthConsumerContext
    {
        ConsumerKey = consumerKey,
        ConsumerSecret = consumerSecret,
        SignatureMethod = SignatureMethod.HmacSha1,
        UseHeaderForOAuthParameters = true

    };

    consumerContext.UseHeaderForOAuthParameters = true;

    //URIs not used - we already have Oauth tokens
    OAuthSession oSession = new OAuthSession(consumerContext, "https://www.example.com",
                            "https://www.example.com",
                            "https://www.example.com");


    oSession.AccessToken = new TokenBase
    {
        Token = accessToken,
        ConsumerKey = consumerKey,
        TokenSecret = accessTokenSecret
    };

    IConsumerRequest consumerRequest = oSession.Request();
    consumerRequest = ConsumerRequestExtensions.ForMethod(consumerRequest, webRequest.Method);
    consumerRequest = ConsumerRequestExtensions.ForUri(consumerRequest, webRequest.RequestUri);
    if (webRequest.Headers.Count > 0)
    {
        ConsumerRequestExtensions.AlterContext(consumerRequest, context => context.Headers = webRequest.Headers);
        if (webRequest.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded")
        {
            Dictionary<string, string> formParameters = new Dictionary<string, string>();
            foreach (string formParameter in requestBody.Split('&'))
            {
                formParameters.Add(formParameter.Split('=')[0], formParameter.Split('=')[1]);
            }
            consumerRequest = consumerRequest.WithFormParameters(formParameters);
        }
    }

    consumerRequest = consumerRequest.SignWithToken();
    return consumerRequest.Context.GenerateOAuthParametersForHeader();
}

您还可以在 StackOverflow 上查看我原来的问题:Query for All Invoices With Open Balances using QuickBooks Online (QBO) Intuit Partner Platform (IPP) DevKit

【讨论】:

    猜你喜欢
    • 2013-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    • 2015-11-07
    • 1970-01-01
    • 1970-01-01
    • 2018-04-22
    相关资源
    最近更新 更多