0

I'm currently trying retrieve list share available in my Azure account from salesforce. I'm trying to implement the example from below sample code:

https://learn.microsoft.com/en-us/rest/api/storageservices/list-shares#samplerequestandresponse

    //private key: access key of my account
   string storageKey =private key;
        string storageName = 'accountName';
        Datetime dt = Datetime.now();
        string formattedDate = dt.formatGMT('EEE, dd MMM yyyy HH:mm:ss')+ ' GMT';
        system.debug('formattedDate--'+formattedDate);

        string CanonicalizedHeaders = 'x-ms-date:'+formattedDate+'\nx-ms-version:2016-05-31';
        string CanonicalizedResource = '/' + storageName + '/\ncomp:list';
        string StringToSign = 'GET\n\n\n\n\n\n\n\n\n\n\n\n' + CanonicalizedHeaders+'\n'+CanonicalizedResource;
        system.debug('StringToSign--'+StringToSign);

        Blob temp = EncodingUtil.base64Decode(storageKey);
        Blob hmac = Crypto.generateMac('HmacSHA256',Blob.valueOf(StringToSign),temp ); //StringToSign
        system.debug('oo-'+EncodingUtil.base64Encode(hmac));
        HttpRequest req = new HttpRequest();
        req.setMethod('GET');
        //req.setHeader('content-type', 'application/xml');
        req.setHeader('x-ms-version','2016-05-31' );
        req.setHeader('x-ms-date', formattedDate);
        string signature = EncodingUtil.base64Encode(hmac);
        string authHeader =  'SharedKey salesforcestrongaccount'+':'+signature;

        req.setHeader('Authorization',authHeader);
        req.setEndpoint('https://<accountName>.file.core.windows.net/?comp=list');

        Http http = new Http();
        HTTPResponse res;
        res = http.send(req);                
        System.debug(LoggingLevel.INFO, 'http.send result status: ' + res.getStatus());

Any help?

Rv1
  • 185
  • 1
  • 10
  • There are issues with the way you are computing `stringToSign`. Please follow the instructions outlined here: https://learn.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services and try your request again. – Gaurav Mantri Aug 31 '17 at 08:21
  • Can you provide any sample codes for that. Because i tried the process specified in the link. Button luck – Rv1 Aug 31 '17 at 08:29
  • No you didn't follow the instructions there. I had a quick glance at your code and figured that out. I suggest you read the documentation again more carefully. Otherwise if you search for Azure Storage REST API examples, I am sure you will find ample source code. – Gaurav Mantri Aug 31 '17 at 08:32
  • I followed the process. I created a MAC key using SHA 256 which you can see in above code. Is there anything i'm missing? – Rv1 Aug 31 '17 at 09:30
  • Please check the documentation for creating stringToSign. That's where you have problem in your code. – Gaurav Mantri Aug 31 '17 at 09:52
  • Yes bro. I know i'm doing mistake in stringToSign. But, I'm unable to identify the mistake. It would great if you can provide help in identifying the issue. – Rv1 Aug 31 '17 at 10:20
  • No offense, but please don't "bro" me. This is how you should be constructing `stringToSign`: `StringToSign = VERB + "\n" + Content-Encoding + "\n" + Content-Language + "\n" + Content-Length + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + If-Modified-Since + "\n" + If-Match + "\n" + If-None-Match + "\n" + If-Unmodified-Since + "\n" + Range + "\n" + CanonicalizedHeaders + CanonicalizedResource;`. This is from the documentation. Now compare this with your code. – Gaurav Mantri Aug 31 '17 at 10:26

1 Answers1

1

As Gaurav Mantri says, there are something wrong with your stringToSign. So you will get this error.

The right Shared Key Authentication is like this:

StringToSign = VERB + "\n" +  
               Content-Encoding + "\n" +  
               Content-Language + "\n" +  
               Content-Length + "\n" +  
               Content-MD5 + "\n" +  
               Content-Type + "\n" +  
               Date + "\n" +  
               If-Modified-Since + "\n" +  
               If-Match + "\n" +  
               If-None-Match + "\n" +  
               If-Unmodified-Since + "\n" +  
               Range + "\n" +  
               CanonicalizedHeaders +   
               CanonicalizedResource; 

Here I create a test demo, you could refer to it.

List share:

        string storageAccount = "storage account";
        string accessKey = "accountkey";
        string resourcePath = "?comp=list";
        string uri = @"https://" + storageAccount + ".file.core.windows.net/" + resourcePath;
        // Web request 
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
        request.Method = "GET";
        request.Headers["x-ms-date"] = DateTime.UtcNow.ToString("R", System.Globalization.CultureInfo.InvariantCulture);
        request.Headers["x-ms-version"] = "2015-02-21";
        String stringToSign = "GET\n"
            + "\n" // content encoding
            + "\n" // content language
            + "\n" // content length
            + "\n" // content md5
            + "\n" // content type
            + "\n" // date
            + "\n" // if modified since
            + "\n" // if match
            + "\n" // if none match
            + "\n" // if unmodified since
            + "\n" // range
            + "x-ms-date:" + request.Headers["x-ms-date"] + "\nx-ms-version:2015-02-21\n" // headers
            + "/" + storageAccount + "/" + "\ncomp:list"; // resources

        System.Security.Cryptography.HMACSHA256 hasher = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(accessKey));
        string strAuthorization = "SharedKey " + storageAccount + ":" + System.Convert.ToBase64String(hasher.ComputeHash(System.Text.Encoding.UTF8.GetBytes(stringToSign)));


        request.Headers["Authorization"] = strAuthorization;

        Task<WebResponse> response = request.GetResponseAsync();
        HttpWebResponse responseresult = (HttpWebResponse)response.Result;

        using (System.IO.StreamReader r = new System.IO.StreamReader(responseresult.GetResponseStream()))
        {
            string jsonData = r.ReadToEnd();
            Console.WriteLine(jsonData);
        }

Result:

enter image description here

Java:

private static final String account = "accountname";
    private static final String key = "Key";

    public static void main(String args[]) throws Exception {
//      String urlString = "http://" + account + ".file.core.windows.net/sampleshare/name.txt";
        String urlString = "https://" + account + ".file.core.windows.net/?comp=list";
        HttpURLConnection connection = (HttpURLConnection) (new URL(urlString)).openConnection();
        getFileRequest(connection, account, key);
        connection.connect();
        System.out.println("Response message : " + connection.getResponseMessage());
        System.out.println("Response code : " + connection.getResponseCode());

        BufferedReader br = null;
        if (connection.getResponseCode() != 200) {
            br = new BufferedReader(new InputStreamReader((connection.getErrorStream())));
        } else {
            br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
        }
        System.out.println("Response body : " + br.readLine());
    }

    public static void getFileRequest(HttpURLConnection request, String account, String key) throws Exception {
        SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss");
        fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = fmt.format(Calendar.getInstance().getTime()) + " GMT";
        String stringToSign = "GET\n" + "\n" // content encoding
                + "\n" // content language
                + "\n" // content length
                + "\n" // content md5
                + "\n" // content type
                + "\n" // date
                + "\n" // if modified since
                + "\n" // if match
                + "\n" // if none match
                + "\n" // if unmodified since
                + "\n" // range
                + "x-ms-date:" + date + "\nx-ms-version:2015-02-21\n" // headers
                + "/" + account + request.getURL().getPath() + "\ncomp:list"; // resources
        System.out.println("stringToSign : " + stringToSign);
        String auth = getAuthenticationString(stringToSign);
        request.setRequestMethod("GET");
        request.setRequestProperty("x-ms-date", date);
        request.setRequestProperty("x-ms-version", "2015-02-21");
        request.setRequestProperty("Authorization", auth);
    }

    private static String getAuthenticationString(String stringToSign) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(Base64.decode(key), "HmacSHA256"));
        String authKey = new String(Base64.encode(mac.doFinal(stringToSign.getBytes("UTF-8"))));
        String auth = "SharedKey " + account + ":" + authKey;
        return auth;
    }
Brando Zhang
  • 22,586
  • 6
  • 37
  • 65
  • Can i know on which platform have you executed this? Is it in java? #Brando – Rv1 Sep 04 '17 at 08:36
  • I tried the above stringtosign format but getting the same error with the key @Brando Thang – Rv1 Sep 04 '17 at 11:28
  • Thank You #Brando Zhag. Java code is executing perfectly. But, when i convert the logic into salesforce, I'm still facing 403 error. Any Idea? – Rv1 Sep 06 '17 at 14:11
  • One more observation is, I can able to make a call azure api with postman app to. But,when I use same key n date in sf,its throwing 403 error. – Rv1 Sep 06 '17 at 19:08
  • I guess maybe salesforce add some custom header to your request. I suggest you could use fiddler to get the request to check there are any other request header in it. – Brando Zhang Sep 07 '17 at 00:16