Azure Storage Rest API (Put Blob API) - c#

I am trying to put a blob with Azure rest api. I made a "GET" request successfully but i had issues with "PUT" request. When I try to make "PUT" request i get a 403 error(Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.). I have seen same post in stackoverflow but it didn't help me. Any suggestions?
string uri = string.Format("https://{0}.blob.core.windows.net/{1}/LibraryForm.png", storageAccountName,containerName);
Byte[] requestPayload = File.ReadAllBytes(imagepath);
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, uri)
{ Content = (requestPayload == null) ? null : new ByteArrayContent(requestPayload) })
{
// Add the request headers for x-ms-date and x-ms-version.
DateTime now = DateTime.UtcNow;
httpRequestMessage.Headers.Add("x-ms-date", now.ToString("R", CultureInfo.InvariantCulture));
httpRequestMessage.Headers.Add("x-ms-version", "2020-06-12");
httpRequestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
httpRequestMessage.Headers.Add("x-ms-blob-content-type", "image/png");
// Add the authorization header.
httpRequestMessage.Headers.Authorization = GetAuthorizationHeader(
storageAccountName, storageAccountKey, now, httpRequestMessage);
// Send the request.
using (HttpResponseMessage httpResponseMessage = await new HttpClient().SendAsync(httpRequestMessage))
{
if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
{
var str = httpResponseMessage.Content.ReadAsStringAsync().Result;
return str;
}
}
}
internal static AuthenticationHeaderValue GetAuthorizationHeader(string storageAccountName, string storageAccountKey, DateTime now,
HttpRequestMessage httpRequestMessage, string ifMatch = "", string md5 = "")
{
// This is the raw representation of the message signature.
HttpMethod method = httpRequestMessage.Method;
String MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
method.ToString(),
(method == HttpMethod.Get || method == HttpMethod.Head) ? String.Empty
: httpRequestMessage.Content.Headers.ContentLength.ToString(),
ifMatch,
GetCanonicalizedHeaders(httpRequestMessage),
GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName),
md5);
// Now turn it into a byte array.
byte[] SignatureBytes = Encoding.UTF8.GetBytes(MessageSignature);
// Create the HMACSHA256 version of the storage key.
HMACSHA256 SHA256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey));
// Compute the hash of the SignatureBytes and convert it to a base64 string.
string signature = Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));
// This is the actual header that will be added to the list of request headers.
// You can stop the code here and look at the value of 'authHV' before it is returned.
AuthenticationHeaderValue authHV = new AuthenticationHeaderValue("SharedKey",
storageAccountName + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes)));
return authHV;
}
private static string GetCanonicalizedHeaders(HttpRequestMessage httpRequestMessage)
{
var headers = from kvp in httpRequestMessage.Headers
where kvp.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase)
orderby kvp.Key
select new { Key = kvp.Key.ToLowerInvariant(), kvp.Value };
StringBuilder sb = new StringBuilder();
// Create the string in the right format; this is what makes the headers "canonicalized" --
// it means put in a standard format. http://en.wikipedia.org/wiki/Canonicalization
foreach (var kvp in headers)
{
StringBuilder headerBuilder = new StringBuilder(kvp.Key);
char separator = ':';
// Get the value for each header, strip out \r\n if found, then append it with the key.
foreach (string headerValues in kvp.Value)
{
string trimmedValue = headerValues.TrimStart().Replace("\r\n", String.Empty);
headerBuilder.Append(separator).Append(trimmedValue);
// Set this to a comma; this will only be used
// if there are multiple values for one of the headers.
separator = ',';
}
sb.Append(headerBuilder.ToString()).Append("\n");
}
return sb.ToString();
}
private static string GetCanonicalizedResource(Uri address, string storageAccountName)
{
StringBuilder sb = new StringBuilder("/").Append(storageAccountName).Append(address.AbsolutePath);
// It will have more entries if you have more query parameters.
NameValueCollection values = HttpUtility.ParseQueryString(address.Query);
foreach (var item in values.AllKeys.OrderBy(k => k))
{
sb.Append('\n').Append(item).Append(':').Append(values[item]);
}
return sb.ToString().ToLower();
}

If you want to upload a file to Azure Blob with rest API, please refer to the fooliwng code
I define a class to get ShareKey
internal static class AzureStorageAuthenticationHelper
{
internal static AuthenticationHeaderValue GetAuthorizationHeader(
string storageAccountName, string storageAccountKey, DateTime now,
HttpRequestMessage httpRequestMessage, string ifMatch = "", string md5 = "")
{
// This is the raw representation of the message signature.
HttpMethod method = httpRequestMessage.Method;
String MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
method.ToString(),
(method == HttpMethod.Get || method == HttpMethod.Head) ? String.Empty
: httpRequestMessage.Content.Headers.ContentLength.ToString(),
ifMatch,
GetCanonicalizedHeaders(httpRequestMessage),
GetCanonicalizedResource(httpRequestMessage.RequestUri, storageAccountName),
md5);
// Now turn it into a byte array.
byte[] SignatureBytes = Encoding.UTF8.GetBytes(MessageSignature);
// Create the HMACSHA256 version of the storage key.
HMACSHA256 SHA256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey));
// Compute the hash of the SignatureBytes and convert it to a base64 string.
string signature = Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));
// This is the actual header that will be added to the list of request headers.
// You can stop the code here and look at the value of 'authHV' before it is returned.
AuthenticationHeaderValue authHV = new AuthenticationHeaderValue("SharedKey",
storageAccountName + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes)));
return authHV;
}
private static string GetCanonicalizedHeaders(HttpRequestMessage httpRequestMessage)
{
var headers = from kvp in httpRequestMessage.Headers
where kvp.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase)
orderby kvp.Key
select new { Key = kvp.Key.ToLowerInvariant(), kvp.Value };
StringBuilder sb = new StringBuilder();
// Create the string in the right format; this is what makes the headers "canonicalized" --
// it means put in a standard format. http://en.wikipedia.org/wiki/Canonicalization
foreach (var kvp in headers)
{
StringBuilder headerBuilder = new StringBuilder(kvp.Key);
char separator = ':';
// Get the value for each header, strip out \r\n if found, then append it with the key.
foreach (string headerValues in kvp.Value)
{
string trimmedValue = headerValues.TrimStart().Replace("\r\n", String.Empty);
headerBuilder.Append(separator).Append(trimmedValue);
// Set this to a comma; this will only be used
// if there are multiple values for one of the headers.
separator = ',';
}
sb.Append(headerBuilder.ToString()).Append("\n");
}
return sb.ToString();
}
private static string GetCanonicalizedResource(Uri address, string storageAccountName)
{
// The absolute path is "/" because for we're getting a list of containers.
StringBuilder sb = new StringBuilder("/").Append(storageAccountName).Append(address.AbsolutePath);
// Address.Query is the resource, such as "?comp=list".
// This ends up with a NameValueCollection with 1 entry having key=comp, value=list.
// It will have more entries if you have more query parameters.
NameValueCollection values = HttpUtility.ParseQueryString(address.Query);
foreach (var item in values.AllKeys.OrderBy(k => k))
{
sb.Append('\n').Append(item).Append(':').Append(values[item]);
}
return sb.ToString().ToLower();
}
}
Uplaod
FileInfo fileInfo = new FileInfo("D:\\sampleData\\readsample.jpg");
string blobName = fileInfo.Name;
string contentType = MimeMapping.GetMimeMapping(blobName);
DateTime now = DateTime.UtcNow;
string blobURI = string.Format("https://{0}.blob.core.windows.net/{1}/{2}", StorageAccountName, "test", blobName);
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, blobURI))
{
httpRequestMessage.Headers.Add("x-ms-date", now.ToString("R", CultureInfo.InvariantCulture));
httpRequestMessage.Headers.Add("x-ms-version", "2020-06-12");
httpRequestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
httpRequestMessage.Headers.Add("x-ms-blob-content-type", contentType);
httpRequestMessage.Content = new StreamContent(fileInfo.OpenRead());
httpRequestMessage.Headers.Authorization = AzureStorageAuthenticationHelper.GetAuthorizationHeader(
StorageAccountName, StorageAccountKey, now, httpRequestMessage);
// Send the request.
using (HttpResponseMessage httpResponseMessage = await new HttpClient().SendAsync(httpRequestMessage))
{
// If successful (status code = 200),
// parse the XML response for the container names.
if (httpResponseMessage.StatusCode == HttpStatusCode.Created)
{
Console.WriteLine("OK");
}
}
}
Besides, Using Azure SDK to implement upload progress is a simple way. Regarding how to use azure sdk, please refer to here.

Related

Facebook data deletion callback gives "Unable to confirm request was received"

I just convert PHP sample code to C#. This is my endpoint:
public async Task<IHttpActionResult> Delete([FromBody] FbModel fbModel)
{
var res = fbModel.signed_request.Split(new char[] { '.' }, 2);
var secret = "ababababababababababababa";
if (res.Length > 1)
{
var sig = res[0];
var json = base64decode(res[1]);
var data = Newtonsoft.Json.JsonConvert.DeserializeObject<FacebookDeletionDto>(json);
if (string.IsNullOrEmpty(data.algorithm) || data.algorithm.ToUpper() != "HMAC-SHA256")
throw new Exception("Unknown algorithm:" + data.algorithm + ". Expected HMAC-SHA256");
var expected_sig = hmacSHA256(res[1], secret);
if (expected_sig != sig)
throw new Exception("Invalid signature:" + sig + ". Expected" + expected_sig);
var returnJson = new { Url = $"https://myperfectsite.com/fb/info/{data.user_id}", confirmation_code = $"{data.user_id}" };
return Ok(returnJson);
}
return null;
}
This code running perfectly and gives me json.
My endpoint return URL and confirmation code in JSON. But in facebook confirmation page it gives me this error :"Unable to confirm request was received
"App name" sent an invalid response to your request. Contact "App name" directly to request it delete info it has about you."
Facebook provides only the following info:
Return a JSON response that contains a URL where the user can check the status
of their deletion request and an alphanumeric confirmation code.
The JSON response has the following form:
{ url: '<url>', confirmation_code: '<code>' }
I ran into the same problem where FB would not accept our server response.
We ultimately fixed the problem by outputting the JSON response in EXACTLY the same sample format. Property names lowercase and not quoted, single quotes around the values, and a value that was alphanumeric (no symbols) and not too long (20 char length worked).
EDIT: Looking back at it, looks like we also used a dynamic type for the response.
dynamic response = new ExpandoObject();
response.url = "<url>";
response.confirmation_code = "<code>";
return response;
I notice that while in facebook documentation "expected_sig" must be equal to "sig" which must be the result of base64_url decoding of $encoded_sig in your code "expected_sig" is compared to the sign not decoded.
Hope this could be helpful.
var sig = res[0];
var json = base64decode(res[1]);
...
if (expected_sig != sig)
throw new Exception(...)
Correct should be as follow:
var sig = base64decode(res[0]);
var json = base64decode(res[1]);
...
if (expected_sig != sig)
throw new Exception(...)
Instead of:
Delete([FromBody] FbModel fbModel)
I use:
string signed_request = Request.Form["signed_request"];
Here is my working code:
public async Task<IActionResult> Delete()
{
string signed_request = Request.Form["signed_request"];
if (!String.IsNullOrEmpty(signed_request))
{
string[] split = signed_request.Split('.');
string signatureRaw = base64decode(split[0]);
string dataRaw = base64decode(split[1]);
// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);
byte[] dataBuffer = Convert.FromBase64String(dataRaw);
// JSON object
var json = Encoding.UTF8.GetString(dataBuffer);
byte[] appSecretBytes = Encoding.UTF8.GetBytes("SecretKey");
System.Security.Cryptography.HMAC hmac = new System.Security.Cryptography.HMACSHA256(appSecretBytes);
byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(split[1]));
if (!expectedHash.SequenceEqual(signature))
{
throw new Exception("Invalid signature");
}
var fbUser = JsonConvert.DeserializeObject<FacebookUserDTO>(json);
return Ok(new { url = $"https://myperfectsite.com/fb/info/{fbUser.user_id}", confirmation_code = $"{fbUser.user_id}" });
}
}

How do I connect to the Apple News API from c#?

I'm trying to connect to Apple's News API. When generating the signature the results all appear the same from each of the different examples. However, I keep getting this error:
{"errors":[{"code":"WRONG_SIGNATURE"}]}
Here is my c# code to generate the auth header.
public class Security
{
public static string AuthHeader(string method, string url, object content=null)
{
var apiKeyId = "<YOUR KEY HERE...>"; //we get this value from our settings file.
var apiKeySecret = "<YOUR SECRET HERE...>"; //we get this value from our settings file.
if ( string.IsNullOrEmpty(apiKeyId) || string.IsNullOrEmpty(apiKeySecret)) return string.Empty;
var encoding = new ASCIIEncoding();
var dt = DateTime.Now.ToString(Constants.DateFormat);
var canonicalRequest = string.Format("{0}{1}{2}", method, url, dt);
var key = Convert.FromBase64String(apiKeySecret);
var hmac = new HMACSHA256(key);
var hashed = hmac.ComputeHash(encoding.GetBytes(canonicalRequest));
var signature = Convert.ToBase64String(hashed);
var authorizaton = string.Format(#"HHMAC; key={0}; signature={1}; date={2}", apiKeyId, signature, dt);
return authorizaton;
}
}
Short version of Constants class
public static class Constants
{
public static readonly string ChannelId = "<YOUR CHANNEL ID HERE...>"; //again from our settings file
public static readonly string BaseUrl = "https://news-api.apple.com";
public static readonly string DateFormat = "yyyy-MM-ddTHH:mm:ssK";
}
Short version of Actions class (SendCommand is the method that performs the request)
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}
I'm using the ReadChannel method for testing.
I've also tried examples in php and ruby with no luck.
Any ideas how to do this correctly?
Pasted the authorization string generated from the original code on this post into fiddler and I was able to get a successful response from the Apple News API. It seems like HttpWebRequest isn't including the Authorization header correctly and submitting the same request with the property PreAuthenticate = true corrects this issue (HttpWebRequest.PreAuthenticate). Also, with a GET request the ContentType needs to be omitted so I've added a conditional statement to account for this too.
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.PreAuthenticate = true;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
if(method.Equals("post", StringComparison.InvariantCultureIgnoreCase))
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}
The error complains something is wrong in how we compute the signature. Let's take a look at the Apple's example code to produces a correct signature, which is here:
https://developer.apple.com/documentation/apple_news/apple_news_api/about_the_news_security_model
Unfortunately I only found Python code. I don't know Python, either, but I can figure out enough to adapt it to just show the signature. We'll also need to know exactly what date value was used.
import base64
from hashlib import sha256
import hmac
from datetime import datetime
channel_id = 'cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_id = '240ab880-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_secret = 'HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF='
url = 'https://news-api.apple.com/channels/%s' % channel_id
date = str(datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"))
canonical_request = 'GET' + url + date
key = base64.b64decode(api_key_secret)
hashed = hmac.new(key, canonical_request, sha256)
signature = hashed.digest().encode("base64").rstrip('\n')
print date
print signature
You can see the result here (the short link points to tutorialspoint.com, which was the first online python interpreter I found in Google):
http://tpcg.io/e1W4p1
If you don't trust the link, just know I was able to use it to figure out the following known-correct signature based on these known inputs:
Method: GET
URL: https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF
DateTime: 2018-06-12T18:15:45Z
API Secret: HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=
Signature: f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=
Now we can write C# code we can verity. When we can use those same inputs to produce the same signature result, we will have correct code.
Based on that, I was able to write this C# code:
public static void Main()
{
string method = "GET";
string url = "https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF";
string dateString = "2018-06-12T18:15:45Z";
string apiKeySecret = "HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=";
var MyResult = GetSignature(method, url, dateString, apiKeySecret);
var DocumentedResult = "f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=";
Console.WriteLine(MyResult);
Console.WriteLine(MyResult == DocumentedResult);
}
public static string GetSignature(string method, string url, string dt, string APISecret)
{
var hmac = new HMACSHA256(Convert.FromBase64String(APISecret));
var hashed = hmac.ComputeHash(Encoding.ASCII.GetBytes(method + url + dt));
return Convert.ToBase64String(hashed);
}
Which you can see in action here:
https://dotnetfiddle.net/PQ73Zv
I don't have my own Apple API key to test with, so that's as far as I can take you.
One thing I did notice from the question is Apple's example has a "Z" at the end of the date string which is missing with original code here.

AWS TranslateText REST API call adding signature v4

Actually this is not a question, but an answer for those that are trying to use AWS TranslateText without SDK. I have had many problems until I got a running version.
In my opinion, the AWS documentation for this service is not complete and there are not many examples to check (at least for .Net).
At first, I thought I was not generating the correct V4 signature, but after checking the steps and values once again, I decided to use Postman to call the service. It was very helpful.
Postman can generate the AWS signature! (yeah, I did not know) so finally I noticed the signature value was not the problem.
Checking the request I could see the headers needed and some of its values.
The problem in my case was that I was not sending the "x-amz-target" header.
And by chance I found that the value for this header must be "AWSShineFrontendService_20170701.TranslateText"
(I saw a similar value here https://rdrr.io/cran/aws.translate/src/R/translateHTTP.R)
This other link was also helpful
Ivona Request Signing Issue - signature does not match (AWS Signature Version 4)
So now I have a running version, I want to share my .Net code. I hope it helps :) !!
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web.Script.Serialization;
namespace AWS_TranslateTextTest
{
class AWS_TranslateText
{
// Replace this with your own values
const string AccessKey = "AKI_ADD_YOUR_ACCESSKEY";
const string SecretKey = "ADD_YOUR_SECRETKEY";
static void Main()
{
try
{
string text = "Translate this text from English to German.";
string sourceLang = "en";
string targetLang = "de";
string responseText = TranslateText(text, sourceLang, targetLang);
JObject json = JObject.Parse(responseText);
string translatedText = ""; // to do read response from json or responseText
if (json.ToString().Contains("TranslatedText")){
//To access to the properties in "dot" notation use a dynamic object
dynamic obj = json;
translatedText = obj.TranslatedText.Value;
Console.WriteLine("TranslatedText is: {0}", translatedText);
}
else{
Console.WriteLine("TranslatedText not found in response.");
throw new Exception(json.ToString());
}
}
catch (WebException ex)
{
Console.WriteLine(ex.Message);
if (ex.Response != null){
foreach (string header in ex.Response.Headers)
{
Console.WriteLine("{0}: {1}", header, ex.Response.Headers[header]);
}
using (var responseStream = ex.Response.GetResponseStream())
{
if (responseStream != null)
{
using (var streamReader = new StreamReader(responseStream))
{
Console.WriteLine(streamReader.ReadToEnd());
}
}
} }
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static string TranslateText(string text, string sourceLang, string targetLang)
{
var date = DateTime.UtcNow;
const string algorithm = "AWS4-HMAC-SHA256";
const string regionName = "eu-west-1";
const string serviceName = "translate";
const string method = "POST";
const string canonicalUri = "/";
const string canonicalQueryString = "";
const string x_amz_target_header = "AWSShineFrontendService_20170701.TranslateText";
const string contentType = "application/x-amz-json-1.1";
const string host = serviceName + "." + regionName + ".amazonaws.com";
var obj = new
{
SourceLanguageCode = sourceLang,
TargetLanguageCode = targetLang,
Text = text
};
var requestPayload = new JavaScriptSerializer().Serialize(obj);
var hashedRequestPayload = HexEncode(Hash(ToBytes(requestPayload)));
var dateStamp = date.ToString("yyyyMMdd");
var requestDate = date.ToString("yyyyMMddTHHmmss") + "Z";
var credentialScope = string.Format("{0}/{1}/{2}/aws4_request", dateStamp, regionName, serviceName);
var bytes = ToBytes(requestPayload);
var headers = new SortedDictionary<string, string>
{
{"content-length", bytes.Length.ToString()},
{"content-type", contentType},
{"host", host},
{"x-amz-date", requestDate},
{"x-amz-target", x_amz_target_header}
};
string canonicalHeaders =
string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
string signedHeaders =
string.Join(";", headers.Select(x => x.Key.ToLowerInvariant() ));
// Task 1: Create a Canonical Request For Signature Version 4
var canonicalRequest = method + '\n' + canonicalUri + '\n' + canonicalQueryString +
'\n' + canonicalHeaders + '\n' + signedHeaders + '\n' + hashedRequestPayload;
var hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));
// Task 2: Create a String to Sign for Signature Version 4
// StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HashedCanonicalRequest
var stringToSign = string.Format("{0}\n{1}\n{2}\n{3}", algorithm, requestDate, credentialScope,
hashedCanonicalRequest);
// Task 3: Calculate the AWS Signature Version 4
// HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,"20130913"),"eu-west-1"),"tts"),"aws4_request")
byte[] signingKey = GetSignatureKey(SecretKey, dateStamp, regionName, serviceName);
// signature = HexEncode(HMAC(derived-signing-key, string-to-sign))
var signature = HexEncode(HmacSha256(stringToSign, signingKey));
// Task 4: Prepare a signed request
// Authorization: algorithm Credential=access key ID/credential scope, SignedHeadaers=SignedHeaders, Signature=signature
var authorization =
string.Format("{0} Credential={1}/{2}/{3}/{4}/aws4_request, SignedHeaders={5}, Signature={6}",
algorithm, AccessKey, dateStamp, regionName, serviceName, signedHeaders, signature);
// Send the request
string endpoint = "https://" + host; // + canonicalUri ;
var webRequest = WebRequest.Create(endpoint);
webRequest.Method = method;
webRequest.Timeout = 20000;
webRequest.ContentType = contentType;
webRequest.Headers.Add("X-Amz-Date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
webRequest.Headers.Add("X-Amz-Target", x_amz_target_header);
webRequest.ContentLength = bytes.Length;
using (Stream newStream = webRequest.GetRequestStream())
{
newStream.Write(bytes, 0, bytes.Length);
newStream.Flush();
}
var response = (HttpWebResponse)webRequest.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
if (responseStream != null)
{
using (var streamReader = new StreamReader(responseStream))
{
string res = streamReader.ReadToEnd();
return res;
}
}
}
return null;
}
private static byte[] GetSignatureKey(String key, String dateStamp, String regionName, String serviceName)
{
byte[] kDate = HmacSha256(dateStamp, ToBytes("AWS4" + key));
byte[] kRegion = HmacSha256(regionName, kDate);
byte[] kService = HmacSha256(serviceName, kRegion);
return HmacSha256("aws4_request", kService);
}
private static byte[] ToBytes(string str)
{
return Encoding.UTF8.GetBytes(str.ToCharArray());
}
private static string HexEncode(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
}
private static byte[] Hash(byte[] bytes)
{
return SHA256.Create().ComputeHash(bytes);
}
private static byte[] HmacSha256(String data, byte[] key)
{
return new HMACSHA256(key).ComputeHash(ToBytes(data));
}
}
}

Google API - Gmail. Downloading attachments

When calling this method please help by advising what values to pass through such as GmailService? Im guessing userID is the Gmail account and the messageID( i want to download all of them) .
How can i change this to download all the attachments in the inbox.
Thank you in advance and I hope someone can help me.
Method im using is below.
public static void GetAttachments(GmailService service, String userId, String messageId, String outputDir)
{
try
{
Message message = service.Users.Messages.Get(userId, messageId).Execute();
IList<MessagePart> parts = message.Payload.Parts;
foreach (MessagePart part in parts)
{
if (!String.IsNullOrEmpty(part.Filename))
{
String attId = part.Body.AttachmentId;
MessagePartBody attachPart = service.Users.Messages.Attachments.Get(userId, messageId, attId).Execute();
// Converting from RFC 4648 base64 to base64url encoding
// see http://en.wikipedia.org/wiki/Base64#Implementations_and_history
String attachData = attachPart.Data.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
File.WriteAllBytes(Path.Combine(outputDir, part.Filename), data);
}
}
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
}
I think the answer to your question can be solved using Get in PostMan
In your header in PostMan, use key as Authorization and pass your token generated to it as value
or goto Authorization and pick bearer token and pass your token.
Note that messageId is the Id in string of the message you are trying to fetch and {userId} =me or user according to google and I believe you can fetch Attachment Id by the method you use above.
https://www.googleapis.com/gmail/v1/users/me/messages/messageId/attachments/id
public async Task<TResult> GetGmailInboxAttachmentById<TResult>(string messageId, string token, string id, string attachId)
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var url = "https://www.googleapis.com/gmail/v1/users/me/messages/" + messageId + "/" + id + "/attachments" + "/" + attachId;
HttpResponseMessage response = await httpClient.GetAsync(url);
return await response.Content.ReadAsAsync<TResult>();
}
}
foreach (var part in inbox.Payload.Parts)
{
if (!String.IsNullOrEmpty(part.Filename))
{
var attachId = part.Body.AttachmentId;
var attach = _gmailService.GetGmailInboxAttachmentById<MessagePartBody>(id, token, part.PartId, attachId).Result;
// Converting from RFC 4648 base64 to base64url encoding
// see http://en.wikipedia.org/wiki/Base64#Implementations_and_history
string attachData = attach.Data.Replace('_', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
string file = Convert.ToBase64String(data);
GetAttach.Add(file);
}
}
Hope this solve it for you because I solved it with this method
I know, it's could be an off-topic, but using PostMan in case when programmer use Google API is little but contraproductive :) APIs also have attachments.Get Method, and you will not need to call HTTP request and allow non OAuth 2.0 authentication especially when you're using multi-phase authentication.
Here is example:
https://developers.google.com/gmail/api/v1/reference/users/messages/attachments/get
foreach (var part in m.Payload.Parts)
{
if (!string.IsNullOrEmpty(part.Filename))
{
var attachId = part.Body.AttachmentId;
MessagePartBody attachPart = service.Users.Messages.Attachments.Get(userId,
message.Id,
attachId).Execute();
byte[] data = GetBytesFromPart(attachPart.Data);
File.WriteAllBytes(Path.Combine(#"c:\teste\",
$"{DateTime.Now:yyyyMMddHHmmss}-{part.Filename}"), data);
}
}
private static string DecodedString(string messagePart)
{
try
{
var data = GetBytesFromPart(messagePart);
string decodedString = Encoding.UTF8.GetString(data);
return decodedString;
}
catch (System.Exception e)
{
// ignored
return string.Empty;
}
}
private static byte[] GetBytesFromPart(string messagePart)
{
var attachData = messagePart.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
return data;
}

How to Add new product to Woocommerce using c# RestSharp

I try to add new product to Woocommerce using c# RestSharp, but answer from server is:
{"errors":[{"code":"woocommerce_api_authentication_error","message":"oauth_consumer_key parameter mangler"}]}
For adding product i use next code:
public string AddProduct()
{
Method method = Method.POST;
string result = "";
string endpoint = "products";
var client = new RestClient(ApiUrl);
var parameters = new Dictionary<string, string>();
var request = createRequestWithParams(parameters, endpoint, method);
request.RequestFormat = DataFormat.Json;
request.AddJsonBody(new DTO.WCProduct { title = "eeee2", type = "simple", regular_price = "777", description = "Descr" });
AddOAuthparams(ref parameters, method.ToString(), endpoint);
result = client.Execute(request).Content;
return result;
}
Where Method createRequestWithParams is :
private RestRequest createRequestWithParams(Dictionary<string, string> parameters, string res, Method methos)
{
var req = new RestRequest(res, methos);
foreach (var item in parameters)
{
req.AddParameter(item.Key, item.Value);
}
return req;
}`
Where Method AddOAuthparams is :
void AddOAuthparams(ref Dictionary<string, string> parameters, string method, string endpoint)
{
parameters["oauth_consumer_key"] = this.ConsumerKey;
parameters["oauth_timestamp"] =
DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds.ToString();
parameters["oauth_timestamp"] = parameters["oauth_timestamp"].Substring(0, parameters["oauth_timestamp"].IndexOf(",")); //todo fix for . or ,
parameters["oauth_nonce"] = Hash(parameters["oauth_timestamp"]);
parameters["oauth_signature_method"] = "HMAC-SHA256";
parameters["oauth_signature"] = GenerateSignature(parameters, method, endpoint);
}
public string GenerateSignature(Dictionary<string, string> parameters, string method, string endpoint)
{
var baserequesturi = Regex.Replace(HttpUtility.UrlEncode(this.ApiUrl + endpoint), "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper());
var normalized = NormalizeParameters(parameters);
var signingstring = string.Format("{0}&{1}&{2}", method, baserequesturi,
string.Join("%26", normalized.OrderBy(x => x.Key).ToList().ConvertAll(x => x.Key + "%3D" + x.Value)));
var signature =
Convert.ToBase64String(HashHMAC(Encoding.UTF8.GetBytes(this.ConsumerSecret),
Encoding.UTF8.GetBytes(signingstring)));
Console.WriteLine(signature);
return signature;
}
private Dictionary<string, string> NormalizeParameters(Dictionary<string, string> parameters)
{
var result = new Dictionary<string, string>();
foreach (var pair in parameters)
{
var key = HttpUtility.UrlEncode(HttpUtility.UrlDecode(pair.Key));
key = Regex.Replace(key, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper()).Replace("%", "%25");
var value = HttpUtility.UrlEncode(HttpUtility.UrlDecode(pair.Value));
value = Regex.Replace(value, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper()).Replace("%", "%25");
result.Add(key, value);
}
return result;
}
** But if i try to Get info about products or Delete some product this functions worked fine, **
This code i find on Github https://github.com/kloon/WooCommerce-REST-API-Client-Library
I think, that my function for signature is worked bad, but i don't understand what should be fixed.
Maybe this is an old question. But it will be worth for someone came from google searching any hint.
edit: From the error message, i think you forgot to include the oauth_consumer_key as the message. Make sure your request include the oauth_consumer_key, by check the RestRequest query parameter.
Btw instead using the implementation from kloon, you can use the Woocommerce C# Library from my repository
Try the below solution its very easy to integrate and require very less line of codes.It Works for me
static void Main(string[] args)
{
string requestURL = #"http://www.example.co.uk/test/wp-json/wc/v1/products";
UriBuilder tokenRequestBuilder = new UriBuilder(requestURL);
var query = HttpUtility.ParseQueryString(tokenRequestBuilder.Query);
query["oauth_consumer_key"] = "consumer_key";
query["oauth_nonce"] = Guid.NewGuid().ToString("N");
query["oauth_signature_method"] = "HMAC-SHA1";
query["oauth_timestamp"] = (Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds)).ToString();
string signature = string.Format("{0}&{1}&{2}", "POST", Uri.EscapeDataString(requestURL), Uri.EscapeDataString(query.ToString()));
string oauth_Signature = "";
using (HMACSHA1 hmac = new HMACSHA1(Encoding.ASCII.GetBytes("consumer_Secret&")))
{
byte[] hashPayLoad = hmac.ComputeHash(Encoding.ASCII.GetBytes(signature));
oauth_Signature = Convert.ToBase64String(hashPayLoad);
}
query["oauth_signature"] = oauth_Signature;
tokenRequestBuilder.Query = query.ToString();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(tokenRequestBuilder.ToString());
request.ContentType = "application/json; charset=utf-8";
// request.Method = "GET";
request.Method = "POST";
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
string json = File.ReadAllText(#"D:\JsonFile.txt");//File Path for Json String
streamWriter.Write(json);
streamWriter.Flush();
}
var httpResponse = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
}

Categories

Resources