.nethmaczuora

how to create user in zuora api using dotnet framework


i need to create users in zuora using dotnet framework 4.8. But I'm having trouble porting the code from java to c#

here is the java implementation https://knowledgecenter.zuora.com/Zephr/Developer_Documentation/HMAC_Request_Signing_and_Key_Pairs

package io.blaize.api.utilities.security;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

import io.blaize.api.exception.HmacException;

public class HmacSigner {

    public static final String TWO_DIGIT_HEX_FORMAT = "%1$02x";
    private final String algorithm;

    public HmacSigner(String algorithm) 
    {
        if ("SHA256".equals(algorithm)) {
          this.algorithm = "SHA-256";
        } else {
        this.algorithm = algorithm;
      }
    }

   public String signRequest(String secretKey, String body, String path, String query, String method,
                                String timestamp, String nonce) throws HmacException {

    Objects.requireNonNull(secretKey);
    Objects.requireNonNull(body);
    Objects.requireNonNull(path);
    Objects.requireNonNull(query);
    Objects.requireNonNull(method);
    Objects.requireNonNull(timestamp);
    Objects.requireNonNull(nonce);

    try {
        MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
        messageDigest.update(secretKey.getBytes());
        messageDigest.update(body.getBytes());
        messageDigest.update(path.getBytes());
        messageDigest.update(query.getBytes());
        messageDigest.update(method.getBytes());
        messageDigest.update(timestamp.getBytes());
        messageDigest.update(nonce.getBytes());

        byte[] digest = messageDigest.digest();
        StringBuffer hash = new StringBuffer();
        for (byte digestByte : digest) {
            Integer unsignedInteger = new Integer(Byte.toUnsignedInt(digestByte));
            hash.append(String.format(TWO_DIGIT_HEX_FORMAT, unsignedInteger));
        }
        return hash.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new HmacException(e);
        }
    }
}

code for create user:

String protocol = "http";
String host = "admin.test.blaize.io";
String path = "/v3/users";
String method = "POST";
String body = "{\"identifiers\": { \"email_address\": \"test@test.com\" }, \"validators\": { \"password\": \"sup3rsecre!10t\" }}";

String accessKey = "xyz";
String secretKey = loadSecretKeySecurely(accessKey);

String timestamp = String.valueOf(new Date().getTime());
String nonce = UUID.randomUUID().toString();
String query = "";
String hash = new HmacSigner("SHA-256").
    signRequest(secretKey, body, path, query, method, timestamp, nonce);

String authorizationHeaderValue = "ZEPHR-HMAC-SHA256 "
    + accessKey + ":" + timestamp + ":" + nonce + ":" + hash;

// This is a standard library implementation for illustration only
HttpURLConnection connection = (HttpURLConnection) new URL(protocol + "://" + host + path + "?" + query).openConnection();
connection.setRequestMethod(method);
connection.addRequestProperty("Authorization", authorizationHeaderValue);
connection.addRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(body);
outputStream.flush();
outputStream.close();

int status = connection.getResponseCode();
if (status >= 200 && status < 400) {
    System.out.println(new BufferedReader(new  InputStreamReader(connection.getInputStream())).lines().collect(Collectors.joining("\n")));
} else {
System.err.println(status);

}

Below is the code in C# :

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace HMACClient
{
     public class HMACDelegatingHandler : DelegatingHandler
    {
      // First obtained the APP ID and API Key from the server
      // The APIKey MUST be stored securely in db or in the App.Config
      private string APPId = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
      private string APIKey = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = null;
        string requestContentBase64String = string.Empty;
        //Get the Request URI
        string requestUri = HttpUtility.UrlEncode(request.RequestUri.AbsoluteUri.ToLower());
        //Get the Request HTTP Method type
        string requestHttpMethod = request.Method.Method;
        //Calculate UNIX time
        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();
        //Create the random nonce for each request
        string nonce = Guid.NewGuid().ToString("N");
        //Checking if the request contains body, usually will be null wiht HTTP GET and DELETE
        if (request.Content != null)
        {
            // Hashing the request body, so any change in request body will result a different hash
            // we will achieve message integrity
            byte[] content = await request.Content.ReadAsByteArrayAsync();
            MD5 md5 = MD5.Create();
            byte[] requestContentHash = md5.ComputeHash(content);
            requestContentBase64String = Convert.ToBase64String(requestContentHash);
        }
        //Creating the raw signature string by combinging
        //APPId, request Http Method, request Uri, request TimeStamp, nonce, request Content Base64 String
        string signatureRawData = String.Format("{0}{1}{2}{3}{4}{5}", APPId, requestHttpMethod, requestUri, requestTimeStamp, nonce, requestContentBase64String);
        //Converting the APIKey into byte array
        var secretKeyByteArray = Convert.FromBase64String(APIKey);
        //Converting the signatureRawData into byte array
        byte[] signature = Encoding.UTF8.GetBytes(signatureRawData);
        //Generate the hmac signature and set it in the Authorization header
        using (HMACSHA256 hmac = new HMACSHA256(secretKeyByteArray))
        {
            byte[] signatureBytes = hmac.ComputeHash(signature);
            string requestSignatureBase64String = Convert.ToBase64String(signatureBytes);
            //Setting the values in the Authorization header using custom scheme (hmacauth)
            request.Headers.Authorization = new AuthenticationHeaderValue("hmacauth", string.Format("{0}:{1}:{2}:{3}", APPId, requestSignatureBase64String, nonce, requestTimeStamp));
        }
        response = await base.SendAsync(request, cancellationToken);
        return response;
    }
}

}

private async void createUser()
{
 Console.WriteLine("Calling the back-end API");
 string apiBaseAddress = "https://mybusiness.api.zephr.com/";
 HMACDelegatingHandler customDelegatingHandler = new HMACDelegatingHandler();
 HttpClient client = HttpClientFactory.Create(customDelegatingHandler);

string body = "{\"identifiers\": {\"email_address\": \"joe.blow@company.com\"},\"attributes\": {\"first_name\": \"Joe\",\"surname\": \"Blow\"},\"foreign_keys\" : {\"other_id\" : \"0030C00000Xu1LYQAZ\"}}";

HttpResponseMessage response = await client.PostAsync(apiBaseAddress + "v3/users", new StringContent(body, Encoding.UTF8, "application/json"));
if (response.IsSuccessStatusCode)
{
    string responseString = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseString);
    Console.WriteLine("HTTP Status: {0}, Reason {1}. Press ENTER to exit", response.StatusCode, response.ReasonPhrase);
}
else
{
    Console.WriteLine("Failed to call the API. HTTP Status: {0}, Reason {1}", response.StatusCode, response.ReasonPhrase);
 }
}

but get error in this line, input string is not valid base64:

var secretKeyByteArray = Convert.FromBase64String(APIKey);

Solution

  • I did it, if anyone needs to create a user in zuora this the c# code:

    HmacSigner implementation

    public class HmacSigner
    {
        public const string TWO_DIGIT_HEX_FORMAT = "{0:x2}";
    
        public string SignRequest(string secretKey, string body, string path, string query, string method,
                                    string timestamp, string nonce)
        {
            if (secretKey == null || body == null || path == null || query == null || method == null || timestamp == null || nonce == null)
            {
                throw new ArgumentNullException("All input parameters must not be null");
            }
    
            try
            {
                using (var messageDigest = SHA256.Create())
                {
                    byte[] keyBytes = Encoding.UTF8.GetBytes(secretKey);
                    byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
                    byte[] pathBytes = Encoding.UTF8.GetBytes(path);
                    byte[] queryBytes = Encoding.UTF8.GetBytes(query);
                    byte[] methodBytes = Encoding.UTF8.GetBytes(method);
                    byte[] timestampBytes = Encoding.UTF8.GetBytes(timestamp);
                    byte[] nonceBytes = Encoding.UTF8.GetBytes(nonce);
    
                    messageDigest.TransformBlock(keyBytes, 0, keyBytes.Length, keyBytes, 0);
                    messageDigest.TransformBlock(bodyBytes, 0, bodyBytes.Length, bodyBytes, 0);
                    messageDigest.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0);
                    messageDigest.TransformBlock(queryBytes, 0, queryBytes.Length, queryBytes, 0);
                    messageDigest.TransformBlock(methodBytes, 0, methodBytes.Length, methodBytes, 0);
                    messageDigest.TransformBlock(timestampBytes, 0, timestampBytes.Length, timestampBytes, 0);
                    messageDigest.TransformFinalBlock(nonceBytes, 0, nonceBytes.Length);
    
                    byte[] digest = messageDigest.Hash;
                    StringBuilder hash = new StringBuilder();
                    foreach (byte digestByte in digest)
                    {
                        hash.AppendFormat(TWO_DIGIT_HEX_FORMAT, digestByte);
                    }
                    return hash.ToString();
                }
            }
            catch (Exception e)
            {
                throw new ApplicationException("Error occurred while generating HMAC", e);
            }
        }
    }
    

    create user code:

    void CreateUser()
    {
        string protocol = "https";
        string host = "xxx.api.zephr.com";
        string path = "/v3/users";
        string method = "POST";
        string body = "{\"identifiers\": { \"email_address\": \"joe.blow@company.com\" }, \"validators\": { \"password\": \"sup3rsecre!10t\" }}";
    
        string accessKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
        string secretKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
    
        string timestamp = Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds, 0).ToString();
        string nonce = Guid.NewGuid().ToString();
        string query = "";
        string hash = new HmacSigner().SignRequest(secretKey, body, path, query, method, timestamp, nonce);
    
        string authorizationHeaderValue = "ZEPHR-HMAC-SHA256 " + accessKey + ":" + timestamp + ":" + nonce + ":" + hash;
    
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(protocol + "://" + host + path + "?" + query);
        request.Method = method;
        request.Headers.Add("Authorization", authorizationHeaderValue);
        request.ContentType = "application/json";
        var postBody = Encoding.ASCII.GetBytes(body);
        request.GetRequestStream().Write(postBody, 0, postBody.Length);
        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
        int status = (int)response.StatusCode;
        Console.WriteLine(status);
        using (Stream responseStream = response.GetResponseStream())
            {
                StreamReader reader = new StreamReader(responseStream);
                Console.WriteLine(reader.ReadToEnd());
    reader.Close();
            }
    }