7
votes

I'm trying to recreate C# DelegatingHandler inside PostMan.
I've created pre-request script that calculates auth header value.

Currently my script looks like this:

function S4() {
    return (((1+Math.random())*0x10000)|0).toString(16).substring(1); 
}

function GetNonce() {
    return (S4() + S4() + S4()+ S4() + S4() + S4() + S4()+ S4()).toLowerCase();
}

function GetTimeStamp() {
    var d = new Date();
    return Math.round(d.getTime() / 1000);
}

function getAuthHeader(httpMethod, requestUrl, requestBody) {
    var CLIENT_KEY = postman.getEnvironmentVariable('hmac_user');
    var SECRET_KEY = postman.getEnvironmentVariable('hmac_key');
    var AUTH_TYPE = 'HMAC';

    requestUrl = requestUrl.replace(/{{(\w*)}}/g,function(str,key) {return environment[key]});
    requestUrl = requestUrl.toLowerCase();
    var requestTimeStamp = GetTimeStamp();
    var nonce = GetNonce();
    var bodyHash="";

    if (httpMethod == 'GET' || !requestBody) {
        requestBody = ''; 
    } else {
        var md5 = CryptoJS.MD5(requestBody);
        bodyHash = CryptoJS.enc.Base64.stringify(md5);
    }  

    var signatureRawData = [CLIENT_KEY, requestUrl, httpMethod, requestTimeStamp, nonce, bodyHash].join("");

    var key = CryptoJS.enc.Base64.parse(SECRET_KEY);
    var hash = CryptoJS.HmacSHA512(signatureRawData, key);
    var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);

    var header = [CLIENT_KEY, hashInBase64, nonce, requestTimeStamp].join(":");

    return AUTH_TYPE+" "+header;
}

postman.setEnvironmentVariable('hmacAuthHeader', getAuthHeader(request.method, request.url, request.data));

This works perfect for GET requests that don't have any body. However when I send x-www-form-urlencoded request I get unauthorized response (401), because of body hash differences inside C# and Postman.

Inside Postman request.data is a JSON object, but when I investigate request in Fiddler I see it is send as string (see below screenshot) enter image description here

Same thing happens when I send form-data. Inside Postman I've added 3 fields, one with string value, two with files. In Fiddler I can see full request, but inside Postman I can't access those files (see below screenshot) enter image description here

I'm trying to access full request body, because I need to calculate hash out of it.

I have working code in C#, not I want to recreate same requests with Postman.

My question is:
How can I access full request body in pre-request script?

I use this code in C# and it works fine:

internal class HmacClientHandler : DelegatingHandler
{
    private readonly string _applicationId;
    private readonly string _applicationKey;

    public HmacClientHandler(string appId, string appKey)
    {
        _applicationId = appId;
        _applicationKey = appKey;
    }

    protected override async Task<HttpResponseMessage>SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
    {
        HttpResponseMessage response = null;
        string url = Uri.EscapeUriString(request.RequestUri.ToString().ToLowerInvariant());
        string methodName = request.Method.Method;

        DateTime epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
        TimeSpan timeSpan = DateTime.UtcNow - epochStart;
        string requestTimeStamp = Convert.ToUInt64(timeSpan.TotalSeconds).ToString();
        string nonce = Guid.NewGuid().ToString("N");

        string contentBase64String = string.Empty;

        if (request.Content != null)
        {
            byte[] content = await request.Content.ReadAsByteArrayAsync();
            MD5 md5 = MD5.Create();
            byte[] hash = md5.ComputeHash(content);
            contentBase64String = Convert.ToBase64String(hash);
        }

        string authenticationKeyString = string.Format("{0}{1}{2}{3}{4}{5}", _applicationId, url, methodName, requestTimeStamp, nonce, contentBase64String);
        var secretKeyBase64ByteArray = Convert.FromBase64String(_applicationKey);

        using (HMACSHA512 hmac = new HMACSHA512(secretKeyBase64ByteArray))
        {
            byte[] authenticationKeyBytes = Encoding.UTF8.GetBytes(authenticationKeyString);
            byte[] authenticationHash = hmac.ComputeHash(authenticationKeyBytes);
            string hashedBase64String = Convert.ToBase64String(authenticationHash);
            request.Headers.Authorization = new AuthenticationHeaderValue("HMAC", string.Format("{0}:{1}:{2}:{3}", _applicationId, hashedBase64String, nonce, requestTimeStamp));
        }

        response = await base.SendAsync(request, cancellationToken);
        return response;
    }
}
1
Probably it's just not possible (not supported): github.com/postmanlabs/postman-app-support/issues/1050Evk
@Evk thanks for finding this. Sadly I will have to find alternative :/Misiu
@Evk internally Postman calculates hashes from body: github.com/postmanlabs/postman-request/blob/master/lib/… but this isn't available from pre-request scripts.Misiu
Well your hash function is SHA512, and source code by link suggests that it only calculates SHA1 anyway.Evk
@Evk hash function doesn't matter-I can change it. My main problem is that I can't access request body in pre-request script.Misiu

1 Answers

4
votes

The previously quoted issue (#1050) appears to only apply to the request body in binary/file mode (RequestBody.MODES.file). The RequestBody API provides what I need for a similar use case: https://www.postmanlabs.com/postman-collection/RequestBody.html

I believe that if you reference pm.request.body in your pre-request script, it will provide what you're looking for. Specifically, pm.request.body.toString() appears to provide the actual x-www-url-encoded string that will ultimately appear in the request (although, if you use environment variables in your request parameters, these will be emitted unresolved, e.g., {{variable_name}}).

So for your script above, I changed the last line to:

postman.setEnvironmentVariable('hmacAuthHeader', getAuthHeader(request.method, request.url, pm.request.body.toString()));

... and that seems to provide a plausible HMAC. Be sure to pay attention to all of the rules for your hash/HMAC protocol, including sorting of parameters, whitespace handling, etc.

Also, I'm not certain that the RequestBody API is available in all incarnations of Postman, but works swimmingly on my native Windows and OS X editions. Hope this helps!