Dynamics 365 Plugin call External REST API - SSL/TSL could not create channel issue - plugins

I have a task to call the external REST API to get data from third party application.
In that, i have created a C# Console application for tried this and it is working fine and i can get the data from thirty party application via REST API.
The same code used to tried in Dynamics Custom workflow\Plugin, i have got a error below. Please give your valuable suggestion on this.
"System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel."
Note: The below options are tried but no luck.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(AlwaysGoodCertificate);
ServicePointManager.Expect100Continue = true;
Thanks,
Vasanth

It is not clear how you are calling external API from D365 Plugin.
// Call external API
static async Task<bool> CallExternalAPI(Guid beziehungId)
{
bool status = false;
HttpClient apiClient = new HttpClient();
apiClient.DefaultRequestHeaders.Accept.Clear();
apiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string url = #"https://reqres.in/api/users?page=2";
HttpResponseMessage response = await apiClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
}
return status;
}
If your problem with SSL/TLS secure channel you can use WebClient.
// Could not create SSL/TLS secure channel.
// Use (SecurityProtocolType)3072
using (WebClient client = new WebClient())
{
client.Headers.Add(HttpRequestHeader.UserAgent, "AvoidError");
ServicePointManager.Expect100Continue = true;
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
htmlCode = client.DownloadString("MY LINK");
//passing the URL again to function from which to extract the content and compare from above content.
strNewCompanyCode = client.DownloadString("MY LINK");
}

Related

How to pass a file through multiple api in .net

I'm trying to download a file from Sharepoint using a REST API. Because my app is written in .Net Core, and the CSOM library doesn't support it, I've made a "sharepoint proxy" in .Net Framework, which is a single app hosted on Azure.
Now I have a problem, while trying to download a file. I send a request from Postman to my app in .Net Core, which send another request to the sharepoint proxy, which (at last) send a GET request to Sharepoint REST API. In result, I become in Sharepoint proxy a Stream from sharepoint REST API, which I try to forward back to my app. I have no idea, which format should I use to send the file. I tried WebStream, FileStream and byte[], but in each case I got an unreadable file.
Download method in .Net Core App
public async Task<Stream> DownloadFile(SharePointFileUrl spInfo)
{
var restUrl = $"{siteUrl}/downloadFile";
using (var httpClient = new HttpClient())
{
var content = new StringContent(JsonConvert.SerializeObject(spInfo), Encoding.UTF8, "application/json");
var webResponse = await httpClient.PostAsync(restUrl, content);
return await webResponse.Content.ReadAsStreamAsync();
}
}
Endpoint in Sharepoint proxy
public byte[] DownloadFile([FromBody] SharePointFileUrl fileInfo)
{
return _spService.DownloadFile(fileInfo.FileUrl);
}
Download method in Sharepoint proxy
public byte[] DownloadFile(string url)
{
var restUrl = $"{_siteUrl}/_api/web/GetFileByServerRelativeUrl('/{url}')/$value";
var request = CreateBaseRequest("GET", restUrl);
request.Headers.Add("X-RequestDigest", _formDigest);
WebResponse fileResponse = request.GetResponse();
var input = fileResponse.GetResponseStream();
using (MemoryStream ms = new MemoryStream())
{
input.CopyTo(ms);
return ms.ToArray();
}
}
Thank you in advance for any help. Of course, I've googled my problem, but without result.
In your Core app try this.
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri("<file url string>"));
return File(req.GetResponse().GetResponseStream(), "<Content type>", "<download file name>");

Invalid remote certificate in HTTPClient (.net core), but works from Postman

I just created an empty .NET Core Console application, from which I want to reach a 3rd party API via HttpClient. The endpoint requires an SSL certificate, as well as an APIKey and Username in the request's headers. I've setup the call in Postman (and Visual Studio Code's REST Client extension) for testing purposes, and I'm getting a 200 back, with the expected payload. However, when using HttpClient (or RestSharp, for that matter), I'm getting the following exception:
The SSL connection could not be established, see inner exception.
The remote certificate is invalid according to the validation procedure.
Here's my code (mocked in the example the url, apikey, username, certificate base64 string and the proxy my company uses):
using (var request = new HttpRequestMessage(HttpMethod.Get, "validUrl"))
{
var certi = "..."; // base64 string
var bytes = Convert.FromBase64String(certi);
var pfxCert = new X509Certificate2Collection();
pfxCert.Import(bytes, null, X509KeyStorageFlags.MachineKeySet);
request.Headers.Add("apikey", "validApiKey");
request.Headers.Add("username", "myUser");
var handler = new HttpClientHandler();
handler.ClientCertificates.AddRange(pfxCert);
handler.Proxy = new WebProxy("myValidCorporateProxy", false);
using (var httpClient = new HttpClient(handler))
{
using (var response = httpClient.SendAsync(request).Result)
{
var content = response.Content.ReadAsStringAsync().Result;
}
}
}
I also tried to play around with ServicePointManager, with no luck:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11; // tested with Tls, Tls11, Tls12
ServicePointManager.Expect100Continue = true;
Maybe I'm failing to convert the .pfx into a base64 encoded string? I've used to following PowerShell commands:
$fileContentBytes = get-content 'C:\Users\myUser\Desktop\certificate.pfx' -Encoding Byte
[System.Convert]::ToBase64String($fileContentBytes) | Out-File ‘C:\Users\myUser\Desktop\certificate-string.txt’
UPDATE:
Using the answer from this question: .net core API Post exception gives NativeErrorCode 12175
It works:
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
But I feel like this isn't too safe of a workaround (bypassing the validation), isn't it?
UPDATE2
When hitting ServerCertificateCustomValidationCallback, I can apparently see a more useful error: System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch
So, I'm assuming something is either wrong with my certificate, or the server's (but Postman somehow ignores this)? Maybe I should take it up with the 3rd party.

How to call SSRS Rest-Api V1.0 with custom security implemented (NOT SOAP)

I have implemented the custom security on my reporting services 2016 and it displays the login page once the URL for reporting services is typed on browser URL bar (either reports or reportserver)
I am using the following code to pass the Credentials
when i use the code WITHOUT my security extension it works and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"NTLM", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
when i use the code WITH the security extension it doesnt work and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"Basic", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
and i get an Exception saying "The response to this POST request did not contain a 'location' header. That is not supported by this client." when i actually use this credentials
Is "basic" the wrong option ?
Have anyone done this ?
Update 1
Well it turns out that my SSRS is expecting an Authorisation cookie
which i am unable to pass (according to fiddler, there is no cookie)
HttpWebRequest request;
request = (HttpWebRequest)HttpWebRequest.Create("http://mylocalcomputerwithRS/Reports_SQL2016/api/v1.0");
CookieContainer cookieJar = new CookieContainer();
request.CookieContainer = cookieJar;
Cookie authCookie = new Cookie("sqlAuthCookie", "username:password");
authCookie.Domain = ".mydomain.mylocalcomputerwithRS";
if (authCookie != null)
request.CookieContainer.Add(authCookie);
request.Timeout = -1;
HttpWebResponse myHttpWebResponse = (HttpWebResponse)request.GetResponse();
That's how I got it (SSRS 2017; api v2.0). I took the value for the "body" from Fiddler:
var handler = new HttpClientHandler();
var httpClient = new HttpClient(handler);
Assert.AreEqual(0, handler.CookieContainer.Count);
// Create a login form
var body = new Dictionary<string, string>()
{
{"__VIEWSTATE", "9cZYKBmLKR3EbLhJvaf1JI7LZ4cc0244Hpcpzt/2MsDy+ccwNaw9hswvzwepb4InPxvrgR0FJ/TpZWbLZGNEIuD/dmmqy0qXNm5/6VMn9eV+SBbdAhSupsEhmbuTTrg7sjtRig==" },
{"__VIEWSTATEGENERATOR", "480DEEB3"},
{ "__EVENTVALIDATION", "IS0IRlkvSTMCa7SfuB/lrh9f5TpFSB2wpqBZGzpoT/aKGsI5zSjooNO9QvxIh+QIvcbPFDOqTD7R0VDOH8CWkX4T4Fs29e6IL92qPik3euu5QpidxJB14t/WSqBywIMEWXy6lfVTsTWAkkMJRX8DX7OwIhSWZAEbWZUyJRSpXZK5k74jl4x85OZJ19hyfE9qwatskQ=="},
{"txtUserName", "User"},
{"txtPassword", "1"},
{"btnLogin","Войти"}
};
var content = new FormUrlEncodedContent(body);
// POST to login form
var response = await httpClient.PostAsync("http://127.0.0.1:777/ReportServer/Logon.aspx", content);
// Check the cookies created by server
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var cookies = handler.CookieContainer.GetCookies(new Uri("http://127.0.0.1:777/ReportServer"));
Assert.AreEqual("sqlAuthCookie", cookies[0].Name);
// Make new request to secured resource
var myresponse = await httpClient.GetAsync("http://127.0.0.1:777/Reports/api/v2.0/Folders");
var stringContent = await myresponse.Content.ReadAsStringAsync();
Console.Write(stringContent);
As an alternative you can customize SSRS Custom Security Sample quite a bit.
I forked Microsoft's Custom Security Sample to do just what you are describing (needed the functionality at a client long ago and reimplemented as a shareable project on GitHub).
https://github.com/sonrai-LLC/ExtRSAuth
I created a YouTube walkthrough as well to show how one can extend and debug SSRS security with this ExtRSAuth SSRS security assembly https://www.youtube.com/watch?v=tnsWChwW7lA
TL; DR; just bypass the Microsoft example auth check in Login.aspx.cs and put your auth in Page_Load() or Page_Init() event of Login.aspx.cs- wherever you want to perform some custom logging check- and then immediately redirect auth'd user to their requested URI.

How to call Windows Azure Storage RestFul Service without SDK?

I try to use Windows Azure like a Storage fom Salesforce.com.
I cheked the documentation and I only can see call the calls to azure rest api from SDK (Java, .Net, JS, etc) examples.
I need integrate Salesforce with Windows Azure Storage but, Azure don't have a SDK for Salesforce.com
From Salesforce.com is allow the calls to rest services but the process to call Azure Rest Services require one o more librarys.
Exameple:
Authentication for the Azure Storage Services require of:
Headers: Date Header and Authorization Header
The Authorization Header require two elments
SharedKey
Account Name
Authorization="[SharedKey|SharedKeyLite] :"
SharedKey and Account Name give a conversion:
HMAC-SHA256 conversion
over UTF-8 encoded
For this convertion the documentation referes to SDK Librarys in others words Java Class or .Net Class type helper that in Salesforce.com not exist.
Please, I need a example to call the authentification service without sdk
Sorry for my bad English.
Visit: https://learn.microsoft.com/en-us/rest/api/storageservices/fileservices/authentication-for-the-azure-storage-services
I need a example to call the authentification service without sdk
We could generate signature string and specify Authorization header for the request of performing Azure storage services without installing SDK. Here is a simple working sample to list the containers, you could refer to my generateAuthorizationHeader function and Authentication for the Azure Storage Services to construct the signature string.
string StorageAccount = "mystorageaccount";
string StorageKey = "my storage key";
string requestMethod = "GET";
string mxdate = "";
string storageServiceVersion = "2014-02-14";
protected void btnlist_Click(object sender, EventArgs e)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(string.Format(CultureInfo.InvariantCulture,
"https://{0}.blob.core.windows.net/?comp=list",
StorageAccount
));
req.Method = requestMethod;
//specify request header
string AuthorizationHeader = generateAuthorizationHeader();
req.Headers.Add("Authorization", AuthorizationHeader);
req.Headers.Add("x-ms-date", mxdate);
req.Headers.Add("x-ms-version", storageServiceVersion);
using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
{
var stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
string content = reader.ReadToEnd();
StringReader theReader = new StringReader(content);
DataSet theDataSet = new DataSet();
theDataSet.ReadXml(theReader);
DataTable dt = theDataSet.Tables[2];
}
}
public string generateAuthorizationHeader()
{
mxdate = DateTime.UtcNow.ToString("R");
string canonicalizedHeaders = string.Format(
"x-ms-date:{0}\nx-ms-version:{1}",
mxdate,
storageServiceVersion);
string canonicalizedResource = string.Format("/{0}/\ncomp:list", StorageAccount);
string stringToSign = string.Format(
"{0}\n\n\n\n\n\n\n\n\n\n\n\n{1}\n{2}",
requestMethod,
canonicalizedHeaders,
canonicalizedResource);
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(StorageKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
String authorization = String.Format("{0} {1}:{2}",
"SharedKey",
StorageAccount,
signature
);
return authorization;
}
Besides, please refer to Azure Storage Services REST API Reference to know more about programmatic access to Azure Storage Services via REST APIs.
I find a way to solve this.
You should use Shared Sing, here explain me:
Enter to Portal Azure
Open the Account Storage
In the General Information click on "Share sing access"
Enable all permissions that you need (In my case only Enable "File")
Enable all resources permission that you need (In my case onl Enable "Service, Container and Object")
Define and Start Date and End Date (This is the space of time that Shared Key will be valid)
Define protocol type (In my case use HTTPS)
Clic on "Generate SAS" button
After this process you will get a token like this:
?sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-11-28T04:29:49Z&st=2017-02-18T20:29:49Z&spr=https&sig=rt7Loxo1MHGJqp0F6ryLhYAmOdRreyiYT418ybDN2OI%3D
You have to use this Token like Autentication
Example Call Code List a Content:
public with sharing class CallAzureRestDemo {
public string token = '&sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-02-19T04:00:44Z&st=2017-02-18T20:00:44Z&spr=https&sig=GTWGQc5GOAvQ0BIMxMbwUpgag5AmUVjrfZc56nHkhjI%3D';
//public Integer batchSize;
public CallAzureRestDemo(){}
public void getlistcontent(String endpoint)
{
// Create HTTP GET request
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(endpoint+token);
Http http = new Http();
HTTPResponse res;
System.debug(LoggingLevel.INFO, '##RESPONSE: '+res);
// only do this if not running in a test method
if(!Test.isRunningTest())
{
System.debug(LoggingLevel.INFO, 'Sending the message to Azure');
res = http.send(req);
System.debug(LoggingLevel.INFO, 'http.send result status: ' + res.getStatus());
}
else
{
System.debug(LoggingLevel.INFO, 'Running in a test so not sending the message to Azure');
}
}
}
Example TestMethod:
#isTest
private class Test_CallAzureRestDemo {
static testMethod void myUnitTest() {
CallAzureRestDemo oRest = new CallAzureRestDemo();
try{
//Call the method and set endpoint
oRest.getlistcontent('https://accountstoragecomex.file.core.windows.net/?comp=list');
}catch(Exception e){
System.debug('##'+e);
}
}
}
Example to Response:
20:15:47.64 (79388244)|CALLOUT_REQUEST|[100]|System.HttpRequest[Endpoint=https://accountstoragecomex.file.core.windows.net/?comp=list&sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-02-19T04:00:44Z&st=2017-02-18T20:00:44Z&spr=https&sig=GTWGQc5GOAvQ0BIMxMbwUpgag5AmUVjrfZc56nHkhjI%3D, Method=GET]
20:15:47.64 (395755012)|CALLOUT_RESPONSE|[100]|System.HttpResponse[Status=OK, StatusCode=200]
Example Call Service "FILE - Get List Share"
Call To List Content
One more time, Sorry for my bad english.

How to consume REST api in Xamarin.iOS?

I have made a REST API and I want to use it using my Xamarin.iOS application.
Basically I want to call the API from my Xamarin application by sending some arguments to one of my API's function.
I tried the resources available at Xamarin's official website, but I a newbie so I cannot understand how it was done.
The REST API is hosted locally by the network I am using. It is not hosted at a static IP.
Kindly guide me.
You don't really need a fancy plugin if you just want to hit Web Endpoints. I simply use the basic WebRequest API.
var request = WebRequest.CreateHttp(YOUR_URL_HERE);
request.Method = "GET";
request.ContentType = "application/JSON";
request.BeginGetResponse(ResponseComplete, request);
... and then your response method can be something along the lines of...
protected void ResponseComplete(IAsyncResult result)
{
try
{
var request = result.AsyncState as HttpWebRequest;
if (request != null)
{
Debug.WriteLine("Completed query: " + request.RequestUri);
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
Debug.WriteLine("Query Result: " + result);
}
}
}
}
... and if you need to post data you can add request.BeginGetRequestStream(PostData, request); before request.BeginGetResponse(ResponseComplete, request); and make your GetRequestStream handling method something along the lines of...
protected void PostData(IAsyncResult result)
{
var request = result.AsyncState as HttpWebRequest;
if (request != null)
{
using (var postStream = request.EndGetRequestStream(result))
{
var json = JsonConvert.SerializeObject(DATA_TO_POST);
Debug.WriteLine("Posting data: " + json);
var byteArray = Encoding.UTF8.GetBytes(json);
postStream.Write(byteArray, 0, byteArray.Length);
}
}
}
I would recommend Refit, you can install it as a NuGet package. Its pritty simple to use.
Refit allows us to define an interface that describes the API that we're calling, and the Refit framework handles making the call to the service and deserializing the return.
Have a look at this great blog post on how to set it up and other packages that might help you out. http://arteksoftware.com/resilient-network-services-with-xamarin/
I have used RestSharp before but Refit is alot easier to get running.