RestSharp Returning Unauthorized - vb.net

I am having an issue and think I may be missing something with RestSharp.
I am authorizing and getting back a cookie just fine... see below. But then when I call to get the data it returns unauthorized. It works just fine in Postman but not in the code below. I am using a console app and I have tried to send the cookie via AddHeader, AddCookie, and just as a parameter. The responseLogin does contain the correct cookie. Any help would be great.
Dim clientLogin = New RestClient("http://[URI to Authorize]............")
Dim requestLogin = New RestRequest(Method.POST)
requestLogin.AddParameter("application/x-www-form-urlencoded", "[Username and password here.....]", ParameterType.RequestBody)
Dim responseLogin As IRestResponse = clientLogin.Execute(requestLogin)
Dim client = New RestClient("http://[URI to get data]............")
Dim request = New RestRequest(Method.GET)
request.AddHeader("Cookie", responseLogin.Cookies(0).Value.ToString)
request.AddHeader("Accept", "application/json")
Dim response As IRestResponse = client.Execute(request)

The Cookie header needs to contain the name and value for the cookie, e.g.
Dim authCookie = responseLogin.Cookies(0) ' Probably should find by name
request.AddHeader("Cookie", String.Format("{0}={1}", authCookie.Name, authCookie.Value))
However, the documentation (I've never used RestSharp personally) says that RestSharp has automatic support for cookies, so if you reuse the RestClient instance and set the CookieContainer you shouldn't need to do anything to handle the cookies manually (unless you want to, which in some cases may be preferable).
Dim client = New RestClient(New Uri("[Base URI...]"))
client.CookieContainer = New System.Net.CookieContainer()
Dim requestLogin = New RestRequest("[login page path]", Method.POST)
requestLogin.AddParameter("application/x-www-form-urlencoded", "[Username and password here.....]", ParameterType.RequestBody)
Dim responseLogin As IRestResponse = client.Execute(requestLogin)
Dim request = New RestRequest("[data api path", Method.GET)
request.AddHeader("Accept", "application/json")
Dim response As IRestResponse = client.Execute(request)
You could probably just reuse the cookie container with different RestClient instances instead of reusing the client.

I had the same issue on RestClient .NET framework 4.5.2 version.
It turns out you have to implement the IAuthenticator interface.
public class MyAuth : IAuthenticator
{
readonly string _password;
readonly string _passwordKey;
readonly string _username;
readonly string _usernameKey;
public MyAuth(string usernameKey, string username, string passwordKey, string password)
{
_usernameKey = usernameKey;
_username = username;
_passwordKey = passwordKey;
_password = password;
}
public void Authenticate(IRestClient client, IRestRequest request)
=> request
.AddCookie(_usernameKey, _username)
.AddCookie(_passwordKey, _password);
//.AddParameter(_usernameKey, _username)
//.AddParameter(_passwordKey, _password);
}
I did this and my request worked.

Related

Azure AD/Token/Certificates/SQL - I'm lost

I started a company 3 months ago and there is no software (CRM+other business tasks) that meets my needs. Reluctantly I decided to write my own 2 months ago and it is now 80% complete! I've been pointed here to Stack Overflow throughout my journey many times and you guys have been a great help -- first and foremost thank you! I've been able to stay quiet until now...
The problem:
I have connection strings all over the place with SQL Authentication username/password exposed. My business partner is across the country so I need to give him access and open a port etc. I've made the move to Microsoft Azure (all setup with a VPN gateway, VM's are running and linked with Azure AD) to lockdown the environment a bit. I'd like to take full advantage of what it offers and use Active Directory for authentication on login (MFA) and then use the token in my query strings removing the need for a hard coded connection string with username/passwords...
I've done a lot of research and it seems I can accomplish what I want but I can't quite figure it out.
App is written in VB.net and most Microsoft docs on the topic are in C#, requiring edits to the app.xaml.cs file which doesn't exist in vb.net, and even if I got around that piece i'm not sure how to configure the sql connection strings to use the token.
Once I get this out of the way I can get back to finishing the software so I can get on the phone and help us take off before the money runs out! Your help is greatly appreciated!
EDIT :
I'm going this route because from what I've found, I can't use Authentication = Active Directory Integrated in my sql string because my AD is not federated (no on-premise ADFS), Active Directory Password still requires the sql admin setup in Azure to be in the sql string. I looked at key vaults which while doing research led me to registering my app which seems to be exactly what I want and where my questions is derived. Microsoft provides a great example downloadable project but again its in C#...If there is a recommendation on a different option i'm all ears...
EDIT 9/3/19 -- A lot of circling and research...I need to convert this code from c# to vb.net. More information on what the below is and how it is broken down from its creator : https://colinsalmcorner.com/post/configuring-aad-authentication-to-azure-sql-databases
public async Task<string> GetAccessTokenAsync(string clientId, string clientSecret, string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority, TokenCache.DefaultShared);
var clientCred = new ClientCredential(clientId, clientSecret); << CAN I MAKE THESE (ClientId, ClientSecret) TIED TO TEXTBOXES FOR USERNAME/PASSWORD ON MY APPLICATION?
var result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
{
throw new InvalidOperationException("Could not get token");
}
return result.AccessToken; <<WHAT DOES RETURN DO HERE?
}
AND
public async Task<SqlConnection> GetSqlConnectionAsync(string tenantId, string clientId, string clientSecret, string dbServer, string dbName)
{
var authority = string.Format("https://login.windows.net/{0}", tenantId);
var resource = "https://database.windows.net/";
var scope = "";
var token = await GetTokenAsync(clientId, clientSecret, authority, resource, scope);
var builder = new SqlConnectionStringBuilder();
builder["Data Source"] = $"{dbServer}.database.windows.net";
builder["Initial Catalog"] = dbName;
builder["Connect Timeout"] = 30;
builder["Persist Security Info"] = false;
builder["TrustServerCertificate"] = false;
builder["Encrypt"] = true;
builder["MultipleActiveResultSets"] = false;
var con = new SqlConnection(builder.ToString());
con.AccessToken = token;
return con;
}
I can understand that you want to use Azure AD access token to connect to Azure SQL DB. Based on the docs you provided and my own research , for now, we can use Azure AD service principle to get access token to connect to Azure SQL only.
I believe you have did some research on it already and find the codes to implement it using C# SDK. However there is no VB.net code sample to do it which confused you .
Honestly , I am not so familiar with VB.net , but I still implemented a demo to do that for you , pls refer to the code below :
Imports System.IO
Imports System.Net
Imports System.Text
Module Module1
Sub Main()
Dim tenant = ""
Dim client_id = ""
Dim username = ""
Dim password = ""
Dim client_secret = ""
Dim sqlserverName = ""
Dim dbname = ""
Dim dbConn = New SqlClient.SqlConnection
dbConn.ConnectionString = "Data Source=" + sqlserverName + ".database.windows.net;Initial Catalog=" + dbname + ";Connect Timeout=30"
dbConn.AccessToken = getAccessToken(tenant, client_id, client_secret, username, password)
Console.WriteLine("User: " + username)
Console.WriteLine("access token value:" + dbConn.AccessToken)
dbConn.Open()
Dim sqlQuery = "select ##version"
Dim Result = New SqlClient.SqlCommand(sqlQuery, dbConn).ExecuteScalar()
Console.WriteLine("query result from Azure SQL : " + Result.ToString)
Console.ReadKey()
End Sub
Function getAccessToken(tenant, client_id, client_secret, username, password) As String
Dim request As HttpWebRequest
request = HttpWebRequest.Create("https://login.microsoftonline.com/" + tenant + "/oauth2/token")
request.Method = "POST"
request.ContentType = "application/x-www-form-urlencoded"
Dim requestBody =
"grant_type=client_credentials" +
"&client_id=" + client_id +
"&client_secret=" + WebUtility.UrlEncode(client_secret) +
"&username=" + WebUtility.UrlEncode(username) +
"&password=" + WebUtility.UrlDecode(password) +
"&resource=https%3A%2F%2Fdatabase.windows.net%2F"
Dim encoding As New UTF8Encoding
Dim byteData As Byte() = encoding.GetBytes(requestBody)
request.ContentLength = byteData.Length
Dim postreqstream As Stream = request.GetRequestStream()
postreqstream.Write(byteData, 0, byteData.Length)
postreqstream.Close()
Dim result = request.GetResponseAsync().GetAwaiter().GetResult()
Dim result_str = New StreamReader(result.GetResponseStream()).ReadToEnd
Dim tokenResp As TokenResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(Of TokenResponse)(result_str)
Return tokenResp.access_token
End Function
Public Class TokenResponse
Public Property token_type As String
Public Property expires_in As String
Public Property ext_expires_in As String
Public Property expires_on As String
Public Property not_before As String
Public Property resource As String
Public Property access_token As String
End Class
End Module
Pls refer to the steps below to create associated Azure AD apps and fill all params to run it :
Creating a Azure AD app in your tenant ,type its name and click register directly, in my case , the app name is DBadmin:
Note its application ID :
Creating a client secret for this app and note the secret :
Adding Azure SQL user login permission to this app :
Grant permission as an admin:
Go to "Groups" blade ,create an Azure AD group , adding the app we just created at same time :
Go to the Azure SQL server that you want to access , and adding the group we just created in to Azure AD admin of SQL server :
Remember to click save after adding .
Ok, we have done all configs here , just modify the params in code to try to connect to your Azure SQL .
Code result :
Hope it helps . Anyway If you just want to hide connection string in your config file.Key vault should be the best choice for you .

CRM16 - Trigger custom action from WebApi

I've built a custom action in CRM that I need to fire through its WebAPI. The custom action is activated and I got no errors in CRM while creating it.
I try to call this action from a VB.NET application like:
Dim httpch As New HttpClientHandler
Dim requestUri As String = "contacts(1fcfd54a-15d3-e611-80dc-0050569ea396)/Microsoft.Dynamics.CRM.new_addnotetocontact"
httpch.Credentials = New NetworkCredential("username", "password", "domain")
Dim httpClient As New HttpClient(httpch)
httpClient.BaseAddress = New Uri(CRMWebApiUri)
httpClient.Timeout = New TimeSpan(0, 2, 0)
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0")
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0")
httpClient.DefaultRequestHeaders.Add("Prefer", "odata.include-annotations='OData.Community.Display.V1.FormattedValue'")
httpClient.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
Dim jsonNote As JObject = New JObject(New JProperty("NoteTitle", "'Mails have been deleted'"), New JProperty("NoteText", "This contacts SmarterMail data has been deleted due to inactivity"))
Dim postData = New StringContent(jsonNote.ToString(), Encoding.UTF8, "application/json")
Dim retrieveContactResponse As HttpResponseMessage = httpClient.PostAsync(requestUri, postData).Result
What i get back is a status 400 with a message:
Request message has unresolved parameters.
I can make other calls to the same site and get all contacts as an example
What does this mean and how do I fix it ?
What does this mean and how do i fix it ?
Referencing Request message has unresolved parameters.
In CRM when you get this error while calling action. then there may be
three reasons behind that
some parameters you are passing wrong. (make sure action name is correctly pass)
your action is not activated
your action name is duplicate and one action is in active mode and other is in draft.(as this is done from CRM side that one has to be in
draft only two same name action wont be active at same time.)
No. 2 is already taken care of as it was already state that the custom action is activated.
No. 3 is addressed in the linked article and is plausible if you may have imported the actions twice in CRM or inadvertently created two actions with the same name.
To address no.1, I would suggest creating an object model to hold the data to be sent
Public Class Note
Public Property NoteTitle As String
Public Property NoteText As String
End Class
CRM is very finicky about proper parameter formatting. Parameter names are also case sensitive. The '' in the NoteTitle will cause issues when serializing.
Also, if possible use NewtonSoft.Json to craft the JSON payload instead of trying to build it on your own.
'Handler with credentials
Dim httpClientHandler As New HttpClientHandler With {
.Credentials = New NetworkCredential("username", "password", "domain")}
'Create and configure HTTP Client
Dim httpClient As New HttpClient(httpClientHandler) With {
.BaseAddress = New Uri(CRMWebApiUri),
.Timeout = New TimeSpan(0, 2, 0)}
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0")
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0")
httpClient.DefaultRequestHeaders.Add("Prefer", "odata.include-annotations='OData.Community.Display.V1.FormattedValue'")
httpClient.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
'Create and populate data to be sent
Dim model As New Note With {
.NoteTitle = "Mails have been deleted",
.NoteText = "This contacts SmarterMail data has been deleted due to inactivity"}
'Serialize mode to well formed JSON
Dim json As String = JsonConvert.SerializeObject(model)
Dim postData = New StringContent(json, Encoding.UTF8, "application/json")
'invoking action using the fully qualified namespace of action message
Dim requestUri As String = "contacts(1fcfd54a-15d3-e611-80dc-0050569ea396)/Microsoft.Dynamics.CRM.new_addnotetocontact"
'POST the data
Dim retrieveContactResponse As HttpResponseMessage = Await httpClient.PostAsync(requestUri, postData)
Additional reference Dynamics CRM 2016: Use Web API actions
When invoking a bound function, you must include the full name of the
function including the Microsoft.Dynamics.CRM namespace. If you do not
include the full name, you will get the following error: Status
Code:400 Request message has unresolved parameters.
Change the name new_addnotetocontact into new_AddNoteToContact, it will work.
Dim requestUri As String = "contacts(1fcfd54a-15d3-e611-80dc-0050569ea396)/Microsoft.Dynamics.CRM.new_AddNoteToContact"
Process Schema is like below:
<Action Name="new_AddNoteToContact" IsBound="true">
<Parameter Name="entity" Type="mscrm.contact" Nullable="false" />
<Parameter Name="NoteTitle" Type="Edm.String" Nullable="false" Unicode="false" />
<Parameter Name="NoteText" Type="Edm.String" Nullable="false" Unicode="false" />
<ReturnType Type="mscrm.annotation" Nullable="false" />
</Action>
Unique Name: new_AddNoteToContact
Ref: https://msdn.microsoft.com/en-us/library/mt607600.aspx#Anchor_3
Update: If you would have edited the unique name after creating action, then duplicates will be created in Processes entity. Pls delete the dupes from Adv.Find
I fixed this some time ago but haven't got the time to get back to answer it. In my case what was the issue was the way i made the request it self
Instead of the following way, which is stated in the question:
Dim postData = New StringContent(jsonNote.ToString(), Encoding.UTF8, "application/json")
Dim retrieveContactResponse As HttpResponseMessage = httpClient.PostAsync(requestUri, postData).Result
Instead of using the httpClient.PostAsync method and providing the StringContent object directly, I used the HttpRequestMessage object and giving that the StringContent object, and then provide the HttpRequestMessage object to the SendAsync method of the httpClient, and that seems to have solved my issue, as it is working now. Also notice that in the original question i had quotation marks in the value of the first JProperty in the JObject to be posted, I'm not sure this has anything to do with it though, but just posting it here as it is different from original code:
Dim jsonNote As JObject = New JObject(New JProperty("NoteTitle", "Mails have been deleted"), New JProperty("NoteText", "This contact's SmarterMail data has been deleted automatically due to inactivity on their CRM account"))
...
Dim reqMsg As New HttpRequestMessage(HttpMethod.Post, CRMWebApiUri + requestUri)
reqMsg.Content = New StringContent(jsonNote.ToString(), Encoding.UTF8, "application/json")
Dim retrieveContactResponse As HttpResponseMessage = httpClient.SendAsync(reqMsg).Result

Trying to add all cookies together from a webrequest, but when I print it, it sends back system.net.cookiecontainer

I am struggling getting a cookie, from a website, when I alert the cookie,it just returns: system.net.cookiecontainer Here's how I am trying to get the cookie:
'get the cookie for the post request !important
Dim req As HttpWebRequest = DirectCast(WebRequest.Create("http://www.dailymail.co.uk/home/index.html"), HttpWebRequest)
req.Method = "GET"
'iniate the cookie container for the post request
Dim tmpcookie As New CookieContainer
'get the cookie.
Dim postcookie = DirectCast(req.GetResponse(), HttpWebResponse)
tmpcookie.Add(postcookie.Cookies)
'assign the cookie to use outsie the scope (background worker)
textcookie = tmpcookie.ToString()
but when I alert textcookie I get what I said above :(
tmpcookie is a CookieContainer. You're calling ToString on a CookieContainer, it does what it's specified to do: output the fully qualified type name, "System.Net.CookieContainer".
It's like doing (New List(Of Object)).ToString() - it's going to output "System.Collection.Generics.List", not a string representing every item in that list.
You'll want to iterate the cookies in that container, and concatenate/build (?) a string from each individual cookie in that container.

POST request in WP8 app

I'm creating a VB.NET app for Windows Phone 8 and I'm currently looking for a solution to send a POST request to a page, and to get the response (simply knowing the page content).
I searched on several forums but none of them helped me.
Thank you in advance
Before u have to download this NuGet package HttpClient for Windows Phone and try this code:
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "http://www.google.com/");
var response = await httpClient.SendAsync(request);
var result = response.Content.ReadAsStringAsync().Result;
This will help you.
And u have to use in async method try this.
Async Function AccessTheWebAsync() As Task(Of Integer)
Dim client As HttpClient = New HttpClient()
Dim getStringTask As Task(Of String) = client.GetStringAsync("http://google.com")
Dim urlContents As String = Await getStringTask
Return urlContents.Length
End Function

Windows Phone VB Web Request

does anybody has an idea how I can perform an asynchronus Post Request in VB.Net for Windows Phone 8?
I tried a lot but nothing worked... also this http://msdn.microsoft.com/de-de/library/system.net.httpwebrequest.begingetrequeststream.aspx didn't work.
Thanks a lot.
I had to figure this out for myself a while ago. Let me see what I can do to help.
Posting a web request is actually simpler than that link shows. Here's what I do.
First, I create a MultipartFormDataContent:
Dim form as New MultipartFormDataContent()
Next, I add each string I want to send like this:
form.Add(New StringContent("String to sent"), "name of the string you are sending")
Next, create a HttpClient:
Dim httpClient as HttpClient = new HttpClient()
Next, we'll create a HttpResponseMessage and post your information to the url of your choice:
Dim response as HttpResponseMessage = Await httpClient.PostAsync("www.yoururl.com/wherever", form)
Then, I usually need the response as a string, so I read the response to a string:
Dim responseString as String = Await response.Content.ReadAsStringAsync()
This will give you the response you wanted, if that's what you wanted.
Here's an example of a method I use:
Public Async Function GetItems() As Task
Dim getUrl As String = "https://myapiurl.com/v3/get"
Dim responseText As String = String.Empty
Dim detailType As String = "complete"
Try
Dim httpClient As HttpClient = New HttpClient()
Dim form As New MultipartFormDataContent()
form.Add(New StringContent(roamingSettings.Values("ConsumerKey").ToString()), "consumer_key")
form.Add(New StringContent(roamingSettings.Values("access_token").ToString()), "access_token")
form.Add(New StringContent(detailType.ToString()), "detailType")
Dim response As HttpResponseMessage = Await httpClient.PostAsync(getUrl, form)
responseText = Await response.Content.ReadAsStringAsync()
Catch ex As Exception
End Try
End Function
If you aren't using the Http client libraries, you need to install them like this:
What you need to do to use the HttpClient, is to navigate in Visual Studio, go to Tools->Library Package Manager->Manage Nuget Packages for this solution. When there, search the online section for HttpClient and make sure you have "Include Prerelease" selected in the listbox above the results. (Default is set to "Stable Only")
Then install the package with the ID of Microsoft.Net.Http
Then you'll need to add an Import statement at the beginning of the document you are using it in.
Let me know if this is what you were looking for.
Thanks,
SonofNun