Digest Authentication fails with PUT file upload - c#

I am trying to upload a file to following the API information in this service. Easy Post API.
I am able to successfully send the first GET request with Digest authentication.
I'm getting a 403 - Unauthorized when trying to upload the file with 'PUT'.
This is the code I have. I am using a custom web client to set parameters in the web request.
public class CustomWebClient : WebClient
{
private BingMailConfigOptions ConfigOptions;
public CustomWebClient(BingMailConfigOptions configOptions) : base()
{
ConfigOptions = configOptions;
}
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest)base.GetWebRequest(address);
request.ServicePoint.Expect100Continue = false;
request.Method = "PUT";
request.Credentials = GetCredentialCache(address, ConfigOptions);
return request;
}
public static CredentialCache GetCredentialCache(Uri uri, BingMailConfigOptions options)
{
var credentialCache = new CredentialCache
{
{
new Uri(uri.GetLeftPart(UriPartial.Authority)),
"Digest",
new NetworkCredential(options.AuthUserName, options.AuthPassword, uri.GetLeftPart(UriPartial.Authority))
}
};
return credentialCache;
}
}
// in a separate class.
private void Upload(string sessionId, string filePath)
{
_log.Trace("Trying to upload the file: " + filePath);
var file = new FileInfo(filePath);
if (file.Exists)
{
using (var uploader = new CustomWebClient(ConfigOptions))
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
Uri uri = new Uri("https://bingmail.com.au/" + "direct_upload/{0}/{1}"(sessionId, HttpUtility.UrlEncode(file.Name)));
uploader.UploadFile(uri, "PUT", filePath);
}
}
else
{
throw new Exception("File Not found");
}
}
Can you please tell me what I'm doing wrong or point me in the right direction?
Thanks

I finally figured out a solution. Hope it will help someone someday.
Complete solution except some easy-to-figure-out methods are posted in this gist. Bing-Mail Easy Post Api - Version 1.3
What i did was modified the DigestAuthFixer from https://stackoverflow.com/a/3117042/959245 to support any HTTP method.
Then used that to create the session, when we create the session using DigestAuthFixer it stores the Digest-Auth headers which i can reuse when uploading the files.
using (var client = new WebClient())
{
var uri = new Uri(_easypostHosts[2] + UploadUri.FormatWith(sessionId, HttpUtility.UrlEncode(fileName)));
// get the auth headers which are already stored when we create the session
var digestHeader = DigestAuthFixer.GetDigestHeader(uri.PathAndQuery, "PUT");
// add the auth header to our web client
client.Headers.Add("Authorization", digestHeader);
// trying to use the UploadFile() method doesn't work in this case. so we get the bytes and upload data directly
byte[] fileBytes = File.ReadAllBytes(filePath);
// as a PUT request
var result = client.UploadData(uri, "PUT", fileBytes);
// result is also a byte[].
content = result.Length.ToString();
}

Related

How to use RestSharp's AddFile() method to send httpContext.Request.Files[0]?

I am using RestSharp to call .NET Web API.
I am sending the uploaded excel file to web api. Below code works perfectly fine on local machine.
But on the server, we don't have permission to save the uploaded file.
I am looking for alternate method for restRequest.AddFile("content", location) that accept HttpPostedFileBase postedFile = httpContext.Request.Files[0].
RestClient restClient = new RestClient("http://localhost:56360");
RestRequest restRequest = new RestRequest("api/excelupload/Upload");
int readWriteTimeout = restRequest.ReadWriteTimeout > 0
? restRequest.ReadWriteTimeout
: ReadWriteTimeout;
restRequest.ReadWriteTimeout = readWriteTimeout;
restRequest.Method = Method.POST;
restRequest.AddHeader("Content-Type", "multipart/form-data");
restRequest.AddFile("content", location);
restRequest.AddParameter("DetailLineNumber", "4");
var response = restClient.Execute(restRequest);
File reading on API.
foreach (var content in provider.Contents)
{
if (!string.IsNullOrWhiteSpace(content.Headers.ContentDisposition.FileName))
{
postedData.File = await content.ReadAsByteArrayAsync();
}
}
I have not used RestSharp for a while. Could you use a Stream? Something like this:
public RestRequest CreateUploadFileRequest(string path, string filename, Stream fileStream)
{
var request = new RestRequest (Method.POST);
request.Timeout = int.MaxValue; //test
request.Resource = "/files/{path}";
request.AddParameter ("path", path, ParameterType.UrlSegment);
//Need to add the "file" parameter with the file name
request.AddParameter ("file", filename);
request.AddFile ("file", s => StreamUtils.CopyStream (fileStream, s), filename);
return request;
}

C# RestClient returning "Login Required" when Login Details are specified

Just started at a new company and we all use Jira, the customers are determined to not use it as they don't like it so I have decided to build a simple Windows Form when they can both Log tickets and get Updates and Comments in a nice simple UI.
Now I have never done any coding before 2 weeks ago so it has been a struggle to get my head around both C# and Rest (Have made scripts for basic IT fixes but never anything as complex as this!)
Back onto point, Set up and got a Rest API set up with a Rest Client but everytime I try pull data from a ticket on Jira I get the error:
{"errorMessages":["You do not have the permission to see the specified issue.","Login Required"],"errors":{}}
Here is the code from the Form:
private void button3_Click_1(object sender, EventArgs e)
{
var client = new RestClient("https://jira.eandl.co.uk/rest/api/2/issue/ITREQ-" + textBox1.Text
);
client.Authenticator = new SimpleAuthenticator("username", "abc", "password", "123");
var request = new RestRequest(Method.GET);
request.AddParameter("token", "saga001", ParameterType.UrlSegment);
// request.AddUrlSegment("token", "saga001");
request.OnBeforeDeserialization = resp => { resp.ContentType = "application/json"; };
var queryResult = client.Execute(request);
Console.WriteLine(queryResult.Content);
}
And here is the code from the Rest Client itself:
public Restclient()
{
endPoint = string.Empty;
httpMethod = httpVerb.GET;
}
private string logonAttempt;
public string makeRequest()
{
string strResponseValue = string.Empty;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(endPoint);
request.Method = httpMethod.ToString();
String authHeader = System.Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(userName + ":" + userPassword));
request.Headers.Add("Authorization", authType.ToString() + " " + authHeader);
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
//Process the Response Stream... (Could be JSON, XML ot HTML etc...)
using (Stream responseStream = response.GetResponseStream())
{
if (responseStream != null)
{
using (StreamReader reader = new StreamReader(responseStream))
{
strResponseValue = reader.ReadToEnd();
}//End of Stream Reader
}
}//end of Response Stream
}
catch (Exception ex)
{
strResponseValue = "(\"errorMessages\":[\"" + ex.Message.ToString() + "\"],\"errors\":{}}";
}
finally
{
if(response != null)
{
((IDisposable)response).Dispose();
}
}
return strResponseValue;
}
}
}
Now obviously I am expecting that I have missed something absolutely bigginer as like I said, I've never taken on a project like this before and had 0 experience.
Just looking for someone to bluntly tell me what I'm doing wrong
Changed to this as per answer:
private void button3_Click_1(object sender, EventArgs e)
{
var client = new
RestClient("https://jira.eandl.co.uk/rest/api/2/issue/ITREQ-" + textBox1.Text
);
client.Authenticator = new HttpBasicAuthenticator("username", "password");
var request = new RestRequest(Method.GET);
string authHeader = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes("cdale!" + ":" + "Chantelle95!"));
request.AddHeader("Authorization", "Basic " + authHeader);
request.OnBeforeDeserialization = resp => { resp.ContentType = "application/json"; };
var queryResult = client.Execute(request);
Console.WriteLine(queryResult.Content);
}
By default with the Jira REST API, you can use Basic Authentication or OAuth2. I think that more easy way for you will be to use the Basic one.
I'm not sure why you have a class where you define your custom RestClient since the first block of code uses the RestSharp one from http://restsharp.org.
In this case, you will need to modify your authenticator:
client.Authenticator = new HttpBasicAuthenticator(userName, password);
And I think that you should remove the line where you specify a token. I don't think that it's required.
Finally, the class Restclient doesn't seem to be used, then remove it.
You could also uses what you have created in your custom RestClient and manually specify a Basic header:
string authHeader = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(userName + ":" + userPassword));
request.AddHeader("Authorization", "Basic " + authHeader);
However, it's essentially the behavior of the HttpBasicAuthenticator class.
If you don't want to encode your credentials in every request here is how to do it using cookies.
When requesting the cookie you don't need to add any authorization on the headers. This method will accept a JSON string with the user name and password and the URL. It will return the cookie values.
public async Task<JiraCookie> GetCookieAsync(string myJsonUserNamePassword, string JiraCookieEndpointUrl)
{
using (var client = new HttpClient())
{
var response = await client.PostAsync(
JiraCookieEndpointUrl,
new StringContent(myJsonUserNamePassword, Encoding.UTF8, "application/json"));
var json = response.Content.ReadAsStringAsync().Result;
var jiraCookie= JsonConvert.DeserializeObject<JiraCookie>(json);
return jArr;
}
}
public class JiraCookie
{
public Session session { get; set; }
}
public class Session
{
public string name { get; set; }
public string value { get; set; }
}
When I call it using url: http://[baseJiraUrl]/rest/auth/1/session it returns the following JSON response:
{
"session" : -{
"name" : JSESSIONID,
"value" : cookieValue
}
Keep in mind the URL above is valid in the version of JIRA I'm using and may vary depending on which version you're using. Read the JIRA API documentation for the correct URL for the version you are using. I'm using the following:
https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#auth/1/session
Remember you'll have to store your cookie and use it on every subsequent request.
Check out this answer on how add cookies to your HttpClient request: How do I set a cookie on HttpClient's HttpRequestMessage.
Once you're done with the cookie (logging out) simply send a delete http request with the same URL as the post.
Reference: https://stackoverflow.com/a/49109192/7763903

Getting 404 error only when using .NET framework

I'm trying to create a version in JIRA for a specific project.
I'm able to do the process via Postman by building my requests manually, but it fails with a 404 when creating the version record via .NET.
I'm assuming .NET adds pesky parameters to the request that Postman doesn't do.
The weird thing is that the authentication call works, but the the version creation fails.
Here's the helper I wrote:
public class JIRA
{
private string AuthToken { get; set; }
private const string c_JIRAUrl = "https://org.atlassian.net";
private const string c_LoginUrl = c_JIRAUrl + "/rest/auth/1/session";
private const string c_CreateVersionUrl = c_JIRAUrl + "/rest/api/2/version";
public JIRA()
{
//this works...
var authResponse = ExecuteRequest(c_LoginUrl, "POST", new
{
username = "login",
password = "password"
});
AuthToken = authResponse["session"]["value"].ToString();
}
public void CreateVersion(string name, string projectKey, ProjectEnvironment environment)
{
//lets hardcode the same data I use in Postman for testing purposes...
var createVersionResponse = ExecuteRequest(c_CreateVersionUrl, "POST", new
{
description = "An excellent version",
name = "1.1.2",
archived = false,
released = false,
project = "TEST"
});
}
private JObject ExecuteRequest(string url, string method, object data)
{
HttpWebResponse response;
var jsonDataString = JsonConvert.SerializeObject(data);
byte[] dataBytes = Encoding.Default.GetBytes(jsonDataString);
var responseText = string.Empty;
var wr = (HttpWebRequest)WebRequest.Create(url);
wr.ContentType = "application/json";
if (!string.IsNullOrEmpty(AuthToken))
wr.Headers.Add(HttpRequestHeader.Authorization, $"Bearer {AuthToken}");
wr.Method = method;
wr.ContentLength = dataBytes.Length;
wr.Accept = "application/json";
using (var webStream = wr.GetRequestStream())
{
webStream.Write(dataBytes, 0, dataBytes.Length);
response = (HttpWebResponse)wr.GetResponse();
}
using (var sr = new StreamReader(response.GetResponseStream()))
{
responseText = sr.ReadToEnd();
}
return JObject.Parse(responseText);
}
}
The CreateVersion method always fails with a 404.
As I've said, doing the same (retrieving the token, creating the version) all works in Postman.
Any ideas what's going on ?
Thanks.
Apparently, when retrieving the token (/rest/auth/1/session) the response contains cookies that POSTMAN was sending back in the 2nd request (creating the version). I had to fire up Fiddler to find out it was doing so because its UI was not saying so.
My .NET client was not doing so. When making it do so, it works.
I'm a little miffed that a REST service expects cookies...

How to create Google documents in Box.com using Box SDK

I'm able to create .txt file with below code:
public void CreatFile(string filename,string filetype,string content)
{
var authenticator = new TokenProvider(clientId, clientSecret);
var oAuthToken = authenticator.RefreshAccessToken(refreshToken);
accessToken = oAuthToken.AccessToken;
// Instantiate a BoxManager with your api key and a user's auth token
var boxManager = new BoxManager(accessToken);
// Create a new file in the root folder
boxManager.CreateFile(Folder.Root, filename + filetype, Encoding.UTF8.GetBytes(content));
}
And using below i can create office documents files:
public void UploadFile(string filename,string fileType,string fileUrl)
{
try
{
var authenticator = new TokenProvider(clientId, clientSecret);
var oAuthToken = authenticator.RefreshAccessToken(refreshToken);
accessToken = oAuthToken.AccessToken;
var client = new RestClient("https://upload.box.com/api/2.0");
var request = new RestRequest("files/content", Method.POST);
request.AddParameter("parent_id", Folder.Root);
request.AddHeader("Authorization", "Bearer " + accessToken);
byte[] byteArray = System.IO.File.ReadAllBytes(fileUrl);
request.AddFile("filename", byteArray, filename + fileType);
var responses = client.Execute(request);
var content = responses.Content;
}
catch(Exception e)
{
}
}
But the same above codes is not working, for Google type documents creation.
I have gone thru many online samples, none of those helped for me.
Please.... help me, how to create Google type documents(.gdoc, .gsheet) in Box.com using Box SDK.
It's not possible to create a Google document through the Box API. It is possible to do this through the Box web app.

Cannot access apicontroller method - returns 404

I have 2 c# asp.net projects. 1 is an api. 1 is consuming this api.
My api:
public class MyApiController : ApiController
{
public dynamic ValidateToken(string token)
{
return myValidationMethod(token);
}
}
Consuming my api from another project:
public class MyController : Controller
{
[HttpPost]
public ActionResult ValidateToken(string token)
{
var url = "http://localhost:1234/myapi/validatetoken";
var parameters = "token=" + token;
using (var client = new WebClient())
{
var result = client.UploadString(url, parameters);
return Json(result);
}
}
}
In project 2 where I consume the api, client.UploadString throws a System.Net.WebException - The remote server returned an error: (404) Not Found.
When I test the api with the chrome rest client it works with http://localhost:1234/myapi/validatetoken?token=myToken
Why can WebClient not find it?
Solved
I got this to work thanks to #BrentMannering with a small change to add content length:
var url = "http://localhost:1234/myapi/validatetoken?token=" + token;
var request = WebRequest.Create(url);
request.Method = "POST";
request.ContentLength = 0; //got an error without this line
var response = request.GetResponse();
var data = response.GetResponseStream();
string result;
using (var sr = new StreamReader(data))
{
result = sr.ReadToEnd();
}
return Json(result);
I don't think UploadString is sending the data as params, so the routing engine on the API side cannot map to an action, hence the 404. According to the MSDN documentation the method is encoding to a Byte[] prior to uploading, this could be part of the problem.
Try using the UploadValues method
var url = "http://localhost:1234/myapi/validatetoken";
var nv = new NameValueCollection { { "token", token } };
using (var client = new WebClient())
{
var result = client.UploadValues(url, nv);
return Json(result);
}
Otherwise to mimic the test you are doing with the chrome client use WebRequest
var url = "http://localhost:1234/myapi/validatetoken?token=" + token;
var request = WebRequest.Create(url);
request.Method = "POST";
var data = request.GetResponse().GetResponseStream();
string result = String.Empty;
using (StreamReader sr = new StreamReader(data))
{
result = sr.ReadToEnd();
}
return Json(result);
WebClient.UploadString method sends an Http POST method. Did you try to access your APIController with a POST method from the test client ? I am assuming your test client sent a GET request and it worked.
There is a different overload where you can mention the Action method type.
var url = "http://localhost:1234/myapi/validatetoken";
var parameters = "token=" + token;
string method = "GET"; //or POST as needed
using (var client = new WebClient())
{
var result = client.UploadString(url,method , parameters);
return Json(result);
}

Categories