I'm trying to send emails through mailgun email service. I'm using angular 2. This is what my service looks like:
sendEmail(fromName, fromEmail, message) {
var headers = new Headers();
var recieverMail = "abc#123.se";//service#csc.kth.se"
var subject = "error report submitted by interactive screen"
headers.append("Authorization", "Basic "+btoa("MY_API_KEY"));
headers.append("content-type", "application/x-www-form-urlencoded");
var url = "https://api.mailgun.net/v3/mymailgun.mailgun.org/messages";
var data = "from="+fromName+"&to=" +recieverMail+"&subject="+subject+"&text="+message;
return this.http.post(url,data, {headers: headers});
}
I got two different programs that is using this service and they both passes the service strings but the service only fails for one of them.
Applcation 1 recieves an error message saying that the "from" parameter is missing and it is because the mail data is not interpreted as url-encoded for some reason. It is just interpreted as plain text!
Application 2 successfully sends messages with the same service and input. As you can see, this time the mail data is interpreted as url-encoded and the mailgun API recognizes the mail parameters.
Any suggestions on what might be causing this problem for application 1?
(I know that the data is not 100% identical from the screenshot but the problem that i want to illustrate is that they interpret the mail data differently)
Related
I am able to successfully send requests to a sandbox via postman, given by a provider following their specs (see images below)
Successful request (see below)
In order to do that, aside from the respective headers and parameters (see image 2) I have to add a ssl/Tls certificate (.pfx) given that the server requires a 2 way handshake so it needs SSl client certificate:
Authorization (see below).
Headers (see below)
Body (see below)
Now, I am trying to do ir programatically using dotnet core 6, but I keep running into the same problem:
And here is my code:
public static string GetAccessToken(IConfiguration _config)
{
string UserName = Environment.GetEnvironmentVariable("USER_NAME");
string Password = Environment.GetEnvironmentVariable("PASSWORD");
var client = new RestClient("https://connect2.xyz.com/auth/token");
var request = new RestRequest();
X509Certificate2 FullChainCertificate = new X509Certificate2("Path/to/Cert/cert.pfx", "test");
client.Options.ClientCertificates = new X509CertificateCollection() { FullChainCertificate };
client.Options.Proxy = new WebProxy("connect2.xyz.com");
var restrequest = new RestRequest();
restrequest.Method = Method.Get;
restrequest.AddHeader("Accept", "*/*");
restrequest.AddHeader("Cache-Control", "no-cache");
restrequest.AddHeader("Content-Type", "application/x-www-form-urlencoded");
restrequest.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes($"{UserName}:{Password}")));
restrequest.AddParameter("grant_type", "client_credentials");
RestResponse response = client.Execute(restrequest);
AccessTokenPointClickCare accessToken = JsonConvert.DeserializeObject<AccessTokenPointClickCare>(response.Content);
string strToken = accessToken.access_token;
return strToken;
}
Now, as the error seems to show, it has to do with the certificates (apparently), but I don't know if something in the code is wrong, or if I'm missing something, etc...
It is worth noting that this code did run in someone else's pc with the same set-up, but of course with that person's own pfx, but for the rest, it is essentially the same, and not to mention that it does work on my postman.
Finally, as the title on this question, the only thing I can think it might also be affecting the request is the host. If I reference the postman, there is a field where I have to place the host name of the server https://connect2.xyz.com/auth/token
So made it work by changing to a new Windows 10. Researching in other Stackoverflow threads found the answer: .NET CORE 5 '''HandshakeFailure'" when making HTTPS request
So I conclude it has to do with the cyphers
I'm creating client side Blazor app with Microsoft.OData.Client. When I create new entity like this:
var dataServiceContext = this.ClientFactory.CreateClient<Container>(new Uri("http://localhost:5000/odata"));
var newAsset = new CreateAssetDto()
{
TechnicalName = "from_client_4",
DisplayNameFormat = "format from client",
Icon = "client/icon",
InheritedFrom = Guid.NewGuid(),
IsActive = true,
Translation = new AssetTranslationDto
{
Title = "Client Asset",
Language = "en",
Description = "This is asset from client"
}
};
dataServiceContext.AddToAssets(newAsset);
await dataServiceContext.SaveChangesAsync();
I get an exception stating that response to this POST request is missing Location header. When I run fiddle to see what's going on I can see that it actually made 2 requests.
The first request is POST but doesn't include the body and recieves 204 response.
The second request is the one that actually contains the data creating new Asset and response contains Location header as it should.
I guess OData Client is complaining about Location header missing in the response for the first request (since response for second request does contain the header). But why is it even making the first request?
Any idea how to deal with this problem?
It's possible that the first request is a preflight request sent by the browser. But normally CORS preflight requests are sent using OPTIONS method, not POST. So this case is curious.
I am a contributor to the project but do not have enough reputation to add comments here to get clarifications. Could you create an issue on https://github.com/OData/odata.net ?
I would like to create a PostRequest in my Business Central Extension that authenticates me in my web service and returns me a token. I send my username and password in the body of the request to my web service and I also receive the token in JSON format in the body.I want to create the post request using HttpClient.
I use the following link as a template: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/methods-auto/httpcontent/httpcontent-data-type
procedure sendPostRequest(uri: Text)
var
client: HttpClient;
content: HttpContent;
contentHeaders: HttpHeaders;
response: HttpResponseMessage;
request: HttpRequestMessage;
begin
content.GetHeaders(contentHeaders);
contentHeaders.Clear();
contentHeaders.Add('Content-Type', 'application/json');
request.Content:= content;
request.SetRequestUri(uri);
request.Method := 'POST';
end;
procedure SetURLsToDefault(var MessagingServiceSetup: Record "Messaging Service Setup WMR")
begin
MessagingServiceSetup."Service URL" := '202.212.127:8800';
end;
And I have a couple of questions:
1) the basic url is 202.212.127:8800 for my API gateway. To be able to authenticate myself I have to access 202.212.127:8800/authenticate. Is there a method in which you can create urls?
2) how do I get my username and password in the content?
3) and how do I get the token and can I save it in the field?
can someone tell me how to get the PostRequest up and running?
Common method to create different URLs is like this:
Create a setup table
Create fields like "Base Url", User, Pass etc.
I propose this pattern for your code:
SendRequest(Method; Url; Body)
Begin
...
Couple of functions (Your Api and Auth):
Authenticate()
begin
Method = 'post';
Url = SetupTable."Base Url" + '/authenticate';
Body = (Use AL Json stack and incorporate your user pass)
SendRequest(Method; Url; Body);
end;
Function1()
begin
Method = 'get';
Url = SetupTable."Base Url" + '/apiPath-Function1';
Body = '';
SendRequest(Method; Url; Body);
end
Function2()
begin
Method = 'post';
Url = SetupTable."Base Url" + '/apiPath-Function2';
Body = (Use AL Json stack and incorporate your body structure);
SendRequest(Method; Url; Body);
end;
To get your user pass into the content you need to check the documentation of the Api you're trying to call. It's usually described in details, it can be a simple header for basic authentication or a complex Jwt.
For receiving a token, again you need to check your Api documentation first, but essentially after making a Rest call (like: client.Send(RequestMessage, ResponseMessage); inside your SendRequest method), you get a response back and you can use AL Json stack to carve information out.
This is a fine article on how to proceed:
https://jackmallender.com/2019/03/04/interacting-with-rest-apis-using-json-from-within-business-central-part-1-an-introduction-to-the-httpclient-data-type/
Basically a string could work as an url. Depends on what you want. It is good practice to have a setup for your web service calls, so I am with Babak. You can set up a table in which you store the links, credentials - whatsoever.
and 4) I suggest Waldos Rest App for web service calls. you can download the source here: https://github.com/waldo1001/waldo.restapp
It encapsulated the calls, has helper functions for json handling as well. Using the "REST Helper" Codeunit. You can break down your call to:
local procedure DoCallWebservice(URI: Text; User: Text; Pass: Text; var Token: Text);
var
RESTHelper: Codeunit "REST Helper WLD";
begin
RRESTHelper.Initialize('GET', URI);
RESTHelper.SetContentType('application/json');
RESTHelper.AddBody('{"user":"USERNAME","pass":"PASSWORD"}');
if RESTHelper.Send() then
Token := RESTHelper.GetResponseContentAsText();
end;
Obviously, you need to parse the response (JSONHelper) to your needs. Look at the code of the codeunit, it's more or less self explanatory.
I am working on Square Payment gateway integration in ASp.Net. I want to request card_nonce using information stored in database. Can I directly request for card_nonce without using SqPaymentForm?
I tried to send request using Restsharp request but getting error in response. Below is my code and response from Square Up.
RequestCode:
RestSharp.RestClient ClientNonce = new RestSharp.RestClient("https://connect.squareup.com");
RestSharp.RestRequest RequestNonce = new RestSharp.RestRequest("v2/card_nonce", RestSharp.Method.POST);
RequestNonce.RequestFormat = RestSharp.DataFormat.Json;
RequestNonce.AddHeader("Accept", "application/json");
string jsonBodyNone = "{\"client_id\":\""+sandboxId+"\",\"card_data\":{\"billing_postal_code\":\"73001\",\"cvv\":\"564\",\"exp_month\":\"1\",\"exp_year\":\"2021\",\"number\":\"4532759734545858\"},\"website_url\":\"http://localhost:24584/\"}";
RequestNonce.AddParameter("application/json", jsonBodyNone, RestSharp.ParameterType.RequestBody);
RestSharp.IRestResponse responseNonce = ClientNonce.Execute(RequestNonce);
System.Net.HttpStatusCode getresponseNonce = responseNonce.StatusCode;
ResponseFromSquare:
{"errors":[{"category":"INVALID_REQUEST_ERROR","code":"BAD_REQUEST","detail":"Your request could not be processed"}]}
Square has designed the API such that merchant sites do not store, process or transmit cardholder data. As a result,it is not possible to generate a card nonce without using SqPaymentForm.
I am having to re-write an existing REST API using .NET (originally written with Ruby). From the client's perspective, it has to work exactly the same way as the old API - i.e. the client code mustn't need to change. The current API requires Basic Authentication. So to call the old API, the following works perfectly:-
var wc = new System.Net.WebClient();
var myCache = new CredentialCache();
myCache.Add(new Uri(url), "Basic", new NetworkCredential("XXX", "XXX"));
wc.Credentials = myCache;
var returnBytes = wc.DownloadData("http://xxxx");
(I have had to ommit the real URL / username / password etc for security reasons).
Now I am writing the new API using ASP.Net Web API with MVC4. I have a weird problem and cannot find anybody else with exactly the same problem. In order to support Basic Authentication, I have followed the guidelines here:
http://sixgun.wordpress.com/2012/02/29/asp-net-web-api-basic-authentication/
One thing, I put the code to "hook in the handler" in the Global.asax.cs file in the Application_Start() event (that wasn't explained so I guessed).
Anyway, if I call my API (which I have deployed in IIS) using the above code, the Authorization header is always null, and the above fails with 401 Unauthorized. However, if I manually set the header using this code, it works fine - i.e. the Authorization header now exists and I am able to Authenticate the user.
private void SetBasicAuthHeader(WebClient request, String userName, String userPassword)
{
string authInfo = userName + ":" + userPassword;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
.......
var wc = new System.Net.WebClient();
SetBasicAuthHeader(request, "XXXX", "XXXX");
var returnBytes = wc.DownloadData("http://xxxx");
Although that works, it's no good to me because existing users of the existing API are not going to be manually setting the header.
Reading up on how Basic Authentication works, the initial request is meant to be anonymous, then the client is returned 401, then the client is meant to try again. However if I put a break point in my code, it will never hit the code again in Antony's example. I was expecting my breakpoint to be hit twice.
Any ideas how I can get this to work?
You're expecting the right behavior. System.Net.WebClient does not automatically include the Authorization headers upon initial request. It only sends them when properly challenged by a response, which to my knowledge is a 401 status code and a proper WWW-Authenticate header. See here and here for further info.
I'm assuming your basic authentication handler is not returning the WWW-Authenticate header and as such WebClient never even attempts to send the credentials on a second request. You should be able to watch this in Fiddler or a similar tool.
If your handler did something like this, you should witness the WebClient approach working:
//if is not authenticated or Authorization header is null
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
response.StatusCode = HttpStatusCode.Unauthorized;
response.Headers.Add("WWW-Authenticate", "Basic realm=\"www.whatever.com\"");
return response;
});
//else (is authenticated)
return base.SendAsync(request, cancellationToken);
As you noticed, if you include the Authorization headers on every request (like you did in your alternative approach) then your handler already works as is. So it may be sufficient - it just isn't for WebClient and other clients that operate in the same way.