Cannot find body in HttpWebRequestMessage - post

Currently i'm using the Microsoft OData connected Service to control a WebAPI with HMAC. So far i managed to get the GET methods working. However when i POST a message i need to hash the raw HTTP body and add it to the header. (due to the HMAC signature)
So far i got:
Private WebCon As New Container(New Uri("http://domain/odata/v1"))
WebCon.Configurations.RequestPipeline.OnMessageCreating = Function(args)
Dim request As New HttpWebRequestMessage(args)
'Todo hash content if there is any
Dim contentMd5Hash As String = ""
If args.Method = "POST" Then
'Todo, retrieve raw (JSON) content from the HttpWebRequestMessage so i can do make a MD5 hash of it.
End If
'rest of code thath creates the headers.
End function

At the moment, I do not know the right way to do this. Unless to somehow receive the body of the request before the call, and put it into container.Configurations.RequestPipeline.OnMessageCreating:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// POST api/values/5
[HttpPost("{id}")]
public async Task PostAction(int id, [FromBody] string value)
{
var container = new DefaultContainer(new Uri("https://services.odata.org/V4/(S(qc322lduoxrqt13nhydbdcvx))/TripPinServiceRW/"));
// You need to get request body for HMAC
var postData = new
{
Id = id,
Value = value
};
byte[] requestBody = await new ObjectContent(typeof(object), postData, new JsonMediaTypeFormatter()).ReadAsByteArrayAsync();
container.Configurations.RequestPipeline.OnMessageCreating = (args) =>
{
var request = new HttpWebRequestMessage(args);
// Get the Request URI
string requestUri = HttpUtility.UrlEncode(request.Url.AbsoluteUri.ToLower());
// Calculate UNIX time
var epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
var timeSpan = DateTime.UtcNow - epochStart;
var requestTimeStamp = Convert.ToUInt64(timeSpan.TotalSeconds).ToString();
// Create the random nonce for each request
var nonce = Guid.NewGuid().ToString("N");
// Get request body for not GET requests (with Microsoft.AspNetCore.Http.HttpRequest Request.Body)
var requestContentBase64String = string.Empty;
if (!request.Method.Equals("GET", StringComparison.OrdinalIgnoreCase) && requestBody != null && requestBody.Length != 0)
{
var md5 = MD5.Create();
var requestContentHash = md5.ComputeHash(requestBody);
requestContentBase64String = Convert.ToBase64String(requestContentHash);
}
// Creating the raw signature string by combinging
// APPId, request Http Method, request Uri, request TimeStamp, nonce
var signatureRawData = string.Format("{0}{1}{2}{3}{4}{5}", APPId, request.Method, requestUri, requestTimeStamp, nonce, requestContentBase64String);
// Converting the APIKey into byte array
var secretKeyByteArray = Convert.FromBase64String(APIKey);
// Converting the signatureRawData into byte array
var signature = Encoding.UTF8.GetBytes(signatureRawData);
// Generate the hmac signature and set it in the Authorization header
using (var hmac = new HMACSHA256(secretKeyByteArray))
{
var signatureBytes = hmac.ComputeHash(signature);
var requestSignatureBase64String = Convert.ToBase64String(signatureBytes);
//Setting the values in the Authorization header using custom scheme (hmacauth)
request.SetHeader("Authorization", string.Format("hmacauth {0}:{1}:{2}:{3}", APPId, requestSignatureBase64String, nonce, requestTimeStamp));
// You can add more haeder you need there
}
return request;
};
// Call some OData method with postData
var result = container.CallSomeMethod(postData);
// Add more business logic there
}
}
Can you change server logic to avoid request body in the HMAC authorization header?
Then you can use the HMAC without the request body in the client side.

Related

Bad request when posting to OData Data Entity in Dynamics 365

I've created a public Data Entity in dynamics with the following fields:
I keep getting a bad request response, but I'm not sure why.
I've tried to make a POST request in two ways:
1.
HireAction hireAction = new HireAction() { CompanyName = "DEMF", MovieId = "DEMF-000000014", HireActionStatus = "Created" };
string jsonMessage = JsonConvert.SerializeObject(hireAction);
using (HttpClient client = new HttpClient())
{
HttpRequestMessage requestMessage = new
HttpRequestMessage(HttpMethod.Post, "MyDynamicsEnvironmentName/data/HireActions?cross-company=true");
requestMessage.Content = new StringContent(jsonMessage, Encoding.UTF8, "application/json");
requestMessage.Headers.Add("Authorization", AuthResult.AuthorizationHeader);
HttpResponseMessage response = client.SendAsync(requestMessage).Result;
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
//Logic
}
}
var url = "MyDynamicsEnvironmentName/data/HireActions?cross-company=true";
var req = HttpWebRequest.Create(url);
req.Method = "POST";
req.ContentType = "application/json";
req.Headers["Authorization"] = AuthResult.AuthorizationHeader;
HireAction hireAction = new HireAction() { CompanyName = "DEMF", MovieId = "DEMF-000000014", HireActionId = "12345", HireActionStatus = "Created" };
var jsonSettings = new JsonSerializerSettings
{
DateTimeZoneHandling = DateTimeZoneHandling.Local
};
var postString = "CompanyName='DEMF'" + "&MovieId='DEMF-000000014'" + "&HireActionId=132&HireActionStatus='Created'";
var data = JsonConvert.SerializeObject(postString, jsonSettings);
var bytes = Encoding.Default.GetBytes(postString);
var newStream = req.GetRequestStream();
newStream.Write(bytes, 0, bytes.Length);
newStream.Close();
using (var resp = req.GetResponse())
{
var results = new StreamReader(resp.GetResponseStream()).ReadToEnd();
}
Some keypoints:
-Of course you'd replace MyDynamicsEnvironmentName with the URL for the environment. The URL is correct and verified however, by the fact that GET requests do work
-The Authresult.AuthorizationHeader contains a valid token, also validated by working GET requests
As said before, both of these result in a bad request. Does someone know what is wrong or missing?

HTTP GET request to API behind AzureAD authentication with ASP.NET Core MVC

The code below gets a token which I then use to try and fetch some data from an API which is behind AzureAD authentication.
I get a token back, but when I use it to try and reach the API, I get "login to your account" in apiResponse.
What is wrong with my authorization?
var recoAadAppId = "xxxxxxxxxxxxxx";
var callerAadAppId = "xxxxxxxxxxxxxx";
var callerAadTenantId = "xxxxxxxxxxxxxx";
var token = await AcquireTokenWithSecret(callerAadAppId, callerAadTenantId, recoAadAppId);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(token.CreateAuthorizationHeader());
using (var response = await httpClient.GetAsync("https://redacted/app/rest/buildQueue"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
}
public static Task<AuthenticationResult> AcquireTokenWithSecret(
string callerAadAppId, string callerTenantId, string recoAadAppId)
{
var secret = "mysecret";
var app = ConfidentialClientApplicationBuilder.Create(callerAadAppId).WithAuthority($"https://login.microsoftonline.com/{callerTenantId}").WithClientSecret(secret).Build();
var scopes = new[] { $"{recoAadAppId}/.default" };
return app.AcquireTokenForClient(scopes).ExecuteAsync(CancellationToken.None);
}

Send HTTP Post with .Net Core

how can I set up an HTTP call in asp.net core mvc
$url = "https://prod-25.northeurope.logic.azure.com:443/..."
$parms = #{
Uri = $url
Method = 'post'
ContentType = 'application/json'
body = '{"recipient": "stefan.","body":"Test"}'
}
curl #parms
using
using System.Net.Http;
and your code will be
var url = "http://yoursite.com/Home/Insert";
var data = new {"recipient"= "stefan.", "body"="Test"};
using(var client = new HttpClient())
{
var response = await client.PostAsJsonAsync(url, data);
string responseContent = await response.Content.ReadAsStringAsync(); // only to see response as text ( debug perpose )
var result = await ProcessedResult<TResult>(response); // cast it to TResult or any type that you expect to retrieve
}

How to upload a small file plus metadata with GraphServiceClient to OneDrive with a single POST request?

I would like to upload small files with metadata (DriveItem) attached so that the LastModifiedDateTime property is set properly.
First, my current workaround is this:
var graphFileSystemInfo = new Microsoft.Graph.FileSystemInfo()
{
CreatedDateTime = fileSystemInfo.CreationTimeUtc,
LastAccessedDateTime = fileSystemInfo.LastAccessTimeUtc,
LastModifiedDateTime = fileSystemInfo.LastWriteTimeUtc
};
using (var stream = new System.IO.File.OpenRead(localPath))
{
if (fileSystemInfo.Length <= 4 * 1024 * 1024) // file.Length <= 4 MB
{
var driveItem = new DriveItem()
{
File = new File(),
FileSystemInfo = graphFileSystemInfo,
Name = Path.GetFileName(item.Path)
};
try
{
var newDriveItem = await graphClient.Me.Drive.Root.ItemWithPath(item.Path).Content.Request().PutAsync<DriveItem>(stream);
await graphClient.Me.Drive.Items[newDriveItem.Id].Request().UpdateAsync(driveItem);
}
catch (Exception ex)
{
throw;
}
}
else
{
// large file upload
}
}
This code works by first uploading the content via PutAsync and then updating the metadata via UpdateAsync. I tried to do it vice versa (as suggested here) but then I get the error that no file without content can be created. If I then add content to the DriveItem.Content property, the next error is that the stream's ReadTimeout and WriteTimeout properties cannot be read. With a wrapper class for the FileStream, I can overcome this but then I get the next error: A stream property 'content' has a value in the payload. In OData, stream property must not have a value, it must only use property annotations.
By googling, I found that there is another way to upload data, called multipart upload (link). With this description I tried to use the GraphServiceClient to create such a request. But it seems that this is only fully implemented for OneNote items. I took this code as template and created the following function to mimic the OneNote behavior:
public static async Task UploadSmallFile(GraphServiceClient graphClient, DriveItem driveItem, Stream stream)
{
var jsondata = JsonConvert.SerializeObject(driveItem);
// Create the metadata part.
StringContent stringContent = new StringContent(jsondata, Encoding.UTF8, "application/json");
stringContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("related");
stringContent.Headers.ContentDisposition.Name = "Metadata";
stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
// Create the data part.
var streamContent = new StreamContent(stream);
streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("related");
streamContent.Headers.ContentDisposition.Name = "Data";
streamContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
// Put the multiparts together
string boundary = "MultiPartBoundary32541";
MultipartContent multiPartContent = new MultipartContent("related", boundary);
multiPartContent.Add(stringContent);
multiPartContent.Add(streamContent);
var requestUrl = graphClient.Me.Drive.Items["F4C4DC6C33B9D421!103"].Children.Request().RequestUrl;
// Create the request message and add the content.
HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Post, requestUrl);
hrm.Content = multiPartContent;
// Send the request and get the response.
var response = await graphClient.HttpProvider.SendAsync(hrm);
}
With this code, I get the error Entity only allows writes with a JSON Content-Type header.
What am I doing wrong?
Not sure why the provided error occurs, your example appears to be a valid and corresponds to Request body example
But the alternative option could be considered for this matter, since Microsoft Graph supports JSON batching, the folowing example demonstrates how to upload a file and update its metadata within a single request:
POST https://graph.microsoft.com/v1.0/$batch
Accept: application/json
Content-Type: application/json
{
"requests": [
{
"id":"1",
"method":"PUT",
"url":"/me/drive/root:/Sample.docx:/content",
"headers":{
"Content-Type":"application/octet-stream"
},
},
{
"id":"2",
"method":"PATCH",
"url":"/me/drive/root:/Sample.docx:",
"headers":{
"Content-Type":"application/json; charset=utf-8"
},
"body":{
"fileSystemInfo":{
"lastModifiedDateTime":"2019-08-09T00:49:37.7758742+03:00"
}
},
"dependsOn":["1"]
}
]
}
Here is a C# example
var bytes = System.IO.File.ReadAllBytes(path);
var stream = new MemoryStream(bytes);
var batchRequest = new BatchRequest();
//1.1 construct upload file query
var uploadRequest = graphClient.Me
.Drive
.Root
.ItemWithPath(System.IO.Path.GetFileName(path))
.Content
.Request();
batchRequest.AddQuery(uploadRequest, HttpMethod.Put, new StreamContent(stream));
//1.2 construct update driveItem query
var updateRequest = graphClient.Me
.Drive
.Root
.ItemWithPath(System.IO.Path.GetFileName(path))
.Request();
var driveItem = new DriveItem()
{
FileSystemInfo = new FileSystemInfo()
{
LastModifiedDateTime = DateTimeOffset.UtcNow.AddDays(-1)
}
};
var jsonPayload = new StringContent(graphClient.HttpProvider.Serializer.SerializeObject(driveItem), Encoding.UTF8, "application/json");
batchRequest.AddQuery(updateRequest, new HttpMethod("PATCH"), jsonPayload, true, typeof(Microsoft.Graph.DriveItem));
//2. execute Batch request
var result = await graphClient.SendBatchAsync(batchRequest);
var updatedDriveItem = result[1] as DriveItem;
Console.WriteLine(updatedDriveItem.LastModifiedDateTime);
where SendBatchAsync is an extension method which implements JSON Batching support for Microsoft Graph .NET Client Library

Why is my HttpWebRequest POST method to my WebAPI server failing?

I've successfully received data from my WebAPI project ("GET"), but my attempt to Post is not working. Here is the relevant server/WebAPI code:
public Department Add(Department item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
departments.Add(item);
return item;
}
...which fails on the "departments.Add(item);" line, when this code from the client is invoked:
const string uri = "http://localhost:48614/api/departments";
var dept = new Department();
dept.Id = 8;
dept.AccountId = "99";
dept.DeptName = "Something exceedingly funky";
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
webRequest.Method = "POST";
var deptSerialized = JsonConvert.SerializeObject(dept); // <-- This is JSON.NET; it works (deptSerialized has the JSONized versiono of the Department object created above)
using (StreamWriter sw = new StreamWriter(webRequest.GetRequestStream()))
{
sw.Write(deptSerialized);
}
HttpWebResponse httpWebResponse = webRequest.GetResponse() as HttpWebResponse;
using (StreamReader sr = new StreamReader(httpWebResponse.GetResponseStream()))
{
if (httpWebResponse.StatusCode != HttpStatusCode.OK)
{
string message = String.Format("POST failed. Received HTTP {0}", httpWebResponse.StatusCode);
throw new ApplicationException(message);
}
MessageBox.Show(sr.ReadToEnd());
}
...which fails on the "HttpWebResponse httpWebResponse = webRequest.GetResponse() as HttpWebResponse;" line.
The err msg on the server is that departments is null; deptSerialized is being populated with the JSON "record" so...what is missing here?
UPDATE
Specifying the ContentType did, indeed, solve the dilemma. Also, the StatusCode is "Created", making the code above throw an exception, so I changed it to:
using (StreamReader sr = new StreamReader(httpWebResponse.GetResponseStream()))
{
MessageBox.Show(String.Format("StatusCode == {0}", httpWebResponse.StatusCode));
MessageBox.Show(sr.ReadToEnd());
}
...which shows "StatusCode == Created" followed by the JSON "record" (array member? term.?) I created.
You forgot to set the proper Content-Type request header:
webRequest.ContentType = "application/json";
You wrote some JSON payload in the body of your POST request but how do you expect the Web API server to know that you sent JSON payload and not XML or something else? You need to set the proper Content-Type request header for that matter.

Resources