restsharp / RestSharp

Simple REST and HTTP API Client for .NET
https://restsharp.dev
Apache License 2.0
9.52k stars 2.34k forks source link

OAuth1 - Path Segments with Special Char "!" - Results in Auth Error #2192

Open ktranlbmx opened 1 month ago

ktranlbmx commented 1 month ago

A clear and concise description of what the bug is.

When using the reserved character '!', in a path segment, it returns a 401 unauthorized error. Example: https://.suitetalk.api.netsuite.com/services/rest/record/v1/purchaseOrder//!transform/vendorBill

Note: this is similar to Issue#2126. However, in that case, they were using special characters in the query parameters. However, here we are using reserved characters in the URL path segments.

To Reproduce Note that when using postman, this end point works: https://.suitetalk.api.netsuite.com/services/rest/record/v1/purchaseOrder/30356/!transform/vendorBill

In C#, I am using the RestSharp library to handle authentication. Note that we are already successfully using RestSharp to call many different NetSuite endpoints using OAuth1 authentication. However, for this particular end point, we believe the "!" in the URL may not be handled properly. This seems to be the only difference in this particular case.

This is the code to reproduce the issue:

private static RestRequest GenerateRestRequest(OAuthCredentials oAuthCredentials, string urlPathSegments, Method requestMethodType)
{
    var authenticator = OAuth1Authenticator.ForAccessToken(
        oAuthCredentials.ConsumerKey,
        oAuthCredentials.ConsumerSecret,
        oAuthCredentials.TokenID,
        oAuthCredentials.TokenSecret,
        OAuthSignatureMethod.HmacSha256
        );

    authenticator.Realm = oAuthCredentials.AccountID;

    RestRequest request = new RestRequest(Url.Combine(oAuthCredentials.CompanyURL, urlPathSegments), requestMethodType)
    {
        Authenticator = authenticator
    };

    return request;
}

public static async Task<RestResponse> NetSuiteTransformPOToVendorBill(this RestClient restClient, OAuthCredentials oAuthCredentials, NetSuiteVendorBill netSuitePOToVendorBillTransform, string NetSuitePOInternalID)
{
    string segmentStr = "{segment}";
    RestRequest request = GenerateRestRequest(oAuthCredentials, $"services/rest/record/v1/purchaseOrder/{NetSuitePOInternalID}/{segmentStr}/vendorBill", Method.Post);

    // I believe the "!" mark in this segment may not be handled correctly when generating the nonce or signature
    request.AddUrlSegment("segment", "!transform", false); 
    // Note: I have tried setting encoding to "true" as well
    // request.AddUrlSegment("segment", "!transform", true);

    request.AddHeader("Content-Type", "application/json");
    request.AddHeader("prefer", "transient");
    string serializedNetSuiteVendorBill = JsonConvert.SerializeObject(netSuitePOToVendorBillTransform);

    request.AddJsonBody(serializedNetSuiteVendorBill);

    return await restClient.ExecuteAsync(request);
}

Expected behavior Returns Status: 204 No Content in Postman

Stack trace error="token_rejected", error_description="Invalid login attempt." "title":"Unauthorized" "status":401

Desktop (please complete the following information):

Additional context

alexeyzimarev commented 1 month ago

There was a change released in v111 to handle special characters differently. However, the test that checks the encoding according to RFC 3986 is failing now.

It would be good to add expected and actual signature strings in OAuth1 issues, otherwise it turns to a guess game.

ktranlbmx commented 4 weeks ago

@alexeyzimarev Thank you, V111.2.0 resolved the issue. Through testing, it works if you put the special character directly in the URL now too (no need to put it into a URL Segment).

Here are the signature strings requested:

Successful REST Req with V111.2.0: oauth_signature=""p97P4FkwbKkvLI5AirFV5Flgb%2BcdsdGUWnSg2d3zQeo%3D""

Unsuccessful REST Req with V110.2.0 oauth_signature=""w5UmuCZysIV4GvDNxMfwXQMbQ5z%2FIFZm1xR7vQ9shQE%3D""

However, I am not sure how useful these are to you without the OAuth credentials used to generate these signatures.

If you have any suggestions on how I could help further, I would be more than happy to help and provide additional info for your unit tests.

alexeyzimarev commented 4 weeks ago

You don't have to provide real credentials. If you look at the tests I added, I used fixed values from Twitter OAuth1 docs that specifies values for everything, then it shows how the signature base will look like, and what the resulting signature will be. That page in their docs allowed me to create a reliable set of tests to verify that the code generates the correct signature base, and the actual signature is correct.

Could you please write a comment if there's any remaining issue now with the latest version? If yes, it'd be nice to have a repro code.