I am trying to upload a file into a SharePoint library by using WebClient and the WebDav .NET library from independentsoft. First I had the problem that the Upload-method returned an exception with HTTP 403. Than I realized that I have to go through a proxy. Now it's returning me HTTP 405. I also used the ClientOM but wasn't able to configure the proxy settings there so I still get a HTTP 403 there.
The code i am using for WebClient and WebDav .NET is the following:
WebClient:
webClient.UseDefaultCredentials = true;
string[] bypassList = { "localhost" };
webClient.Proxy = new WebProxy("proxy", true, bypassList, new NetworkCredential("user", "password"));
try
{
byte[] response = webClient.UploadFile("http://sharepoint/testBlankSite/testLibrary/TestUpload.txt", "PUT", #"...\Desktop\TestUpload.txt");
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
WebDav .NET
NetworkCredential credential = new NetworkCredential("user", "password");
string[] bypassList = { "localhost" };
WebProxy proxy = new WebProxy("proxy", true, bypassList, new NetworkCredential("user", "password"));
WebdavSession session = new WebdavSession(credential, proxy);
Resource resource = new Resource(session);
try
{
resource.Upload("http://sharepoint/testBlankSite/testLibrary/TestUpload.txt", #"...\Desktop\TestUpload.txt");
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Has anyone an idea why this isn't working and how I can solve this?
This is a pretty common problem with SharePoint. Check out these links:
SharePoint, WebDAV, and a Case of the 405 Status Codes
SharePoint 2013 - Disable WebDAV use on SharePoint
As #GavinB and #Jeremy mentioned, the alternative is the client-side object model, i.e. Microsoft.SharePoint.Client. This library is just a wrapper and it performs HTTP requests that are similar to the ones you are trying.
Related
I'm playing with OneDrive SDK 1.1.15.0:
try
{
AppConfig appConfig = new AppConfig
{
MicrosoftAccountAppId = oneDriveClientID, //something like 00000000123456AB
MicrosoftAccountClientSecret = oneDriveClientSecret, //something like 3vx[...]1sJ
MicrosoftAccountReturnUrl = "https://localhost/return",
MicrosoftAccountScopes = new string[] { "wl.signin", "wl.offline_access", "onedrive.readonly" }
};
OneDriveClient oneDriveClient = new OneDriveClient(appConfig);
AccountSession accountSession = await oneDriveClient.AuthenticateAsync();
//more code
await oneDriveClient.SignOutAsync();
}
catch (Exception ex)
{
throw ex;
}
My problem is in line:
AccountSession accountSession = await oneDriveClient.AuthenticateAsync();
that throws the following exception:
Microsoft.OneDrive.Sdk.OneDriveException, AuthenticationFailure: Failed to retrieve a valid authentication token for the user.
Any ideas?
Thank you in advance!
UPDATE
After reading comment from ginach (thank you!), I update my code. Some arguments to underline:
I want to access OneDrive from an Azure worker Role, so no authentication windows or something like that.
I upload the Microsoft.OneDrive SDK to 1.1.20 version.
I already registered my application to the OneDrive dev portal.
My actual code is:
try
{
MicrosoftAccountServiceInfo serviceInfo = new MicrosoftAccountServiceInfo();
serviceInfo.AppId = oneDriveClientID; //something like: 00000000ABCDEFGH
serviceInfo.ClientSecret = oneDriveClientSecret; //something like: 3vx[...]1sJ
serviceInfo.ReturnUrl = oneDriveReturnUrl; //something like: https://localhost/return
serviceInfo.Scopes = oneDriveAccountScopes; //something like new string[] { "wl.signin", "wl.offline_access", "onedrive.readonly" }
MicrosoftAccountAuthenticationProvider authenticationProvider = new MicrosoftAccountAuthenticationProvider(serviceInfo);
OneDriveClient oneDriveClient = await OneDriveClient.GetAuthenticatedMicrosoftAccountClient(oneDriveClientID, oneDriveReturnUrl, oneDriveAccountScopes, authenticationProvider);
//more code
await oneDriveClient.SignOutAsync();
}
catch (OneDriveException odex)
{
throw odex;
}
catch (Exception ex)
{
throw ex;
}
I obtain again and again (in OneDriveClient.GetAuthenticatedMicrosoftAccountClient method) a OneDriveException stating (Error property): AuthenticationFailure - Failed to retrieve a valid authentication token for the user.
Any suggestion?
Thank you.
UPDATE 2
OK, I'm trying a new approach. Using RestSharp I try to login to OneDrive with that code:
string clientId = "00[...]00";
string scopes = "wl.signin, wl.offline_access, onedrive.readonly";
string responseType = "code";
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
RestClient client = new RestClient("https://login.live.com");
RestRequest request = new RestRequest();
request.Method = Method.GET;
request.Resource = "oauth20_authorize.srf";
request.AddQueryParameter("client_id", clientId);
request.AddQueryParameter("scope", scopes);
request.AddQueryParameter("response_type", responseType);
request.AddQueryParameter("redirect_uri", redirectUri);
IRestResponse response = client.Execute(request);
string content = response.Content;
I check the request with Fiddler and what I'm sending is:
https://login.live.com/oauth20_authorize.srf?client_id=00[...]00&scope=wl.signin%20wl.offline_access%20onedrive.readonly&response_type=code&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf
But OneDrive server answers my with:
Microsoft account requires JavaScript to sign in. This web browser either does not support JavaScript, or scripts are being blocked. To find out whether your browser supports JavaScript, or to allow scripts, see the browser's online help.
So I try the request in a browser and OneDrive server redirects me to the authorization page:
Now the question is: is there any workaround to skip the manual authorization?
Thank you,
Attilio
The client requires an authentication provider to be able to retrieve authentication tokens. There are a few ways to do this depending on your current platform.
Create your own IAuthenticationProvider implementation. The authentication provider is responsible for setting the Authentication header on requests. Here's how you would create a client instance with a custom authentication provider:
var client = new OneDriveClient(appConfig, serviceInfoProvider: new
ServiceInfoProvider(new CustomAuthenticationProvider()));
Use one of the various default authentication implementations. Take a look at the SDK authentication documentation for the available options and examples.
If you have a refresh token and only want to do the silent authentication flow you can use OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient. Here's an example:
var client = await OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient(clientId, returnUrl, scopes, refreshToken);
I am working on an application that needs constant internet connectivity in order to function according to specifications.
The problem that I am facing is that if I connect to a VPN then the application is unable to access the internet at all.
Here is a part of the code where I try to check whether the server is reachable or not:
try
{
using (var client = new System.Net.WebClient())
{
//var networkCredentials = new NetworkCredential("shumais", "password");
//WebProxy myProxy = new WebProxy("192.168.0.61", 8080) { Credentials = networkCredentials };
//WebProxy myProxy = new WebProxy("192.168.0.61", 8080);
//client.Proxy = WebRequest.GetSystemWebProxy();
IWebProxy proxy = WebRequest.GetSystemWebProxy();
proxy.Credentials = CredentialCache.DefaultCredentials;
client.UseDefaultCredentials = true;
client.Proxy = proxy;
using (var stream = client.OpenRead(WebUrls.URL_BASE_REQUEST))
{
_isServerReachable = true;
}
}
}
catch (Exception)
{
_isServerReachable = false;
}
I have got the code working with/without proxies and now just need to get the application to work when I am connected to any VPNs.
Any help will be greatly appreciated, thanks.
If you are using a Microsoft PPTP VPN, you need to uncheck "Use default gateway on remote network" in the TCP/IPv4 advanced settings for the VPN connection.
Since you're trying to access the site with the default network credentials, make sure to add the default proxy to the app.config file, and add a cookie container (seriously sounds stupid but it looks like it's helping other people out too).
Check out my answer to this post: How to use IE proxy server settings and its credentials in .Net Application
I have a web application (hosted in IIS) that talks to a Windows service. The Windows service is using the ASP.Net MVC Web API (self-hosted), and so can be communicated with over http using JSON. The web application is configured to do impersonation, the idea being that the user who makes the request to the web application should be the user that the web application uses to make the request to the service. The structure looks like this:
(The user highlighted in red is the user being referred to in the examples below.)
The web application makes requests to the Windows service using an HttpClient:
var httpClient = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
httpClient.GetStringAsync("http://localhost/some/endpoint/");
This makes the request to the Windows service, but does not pass the credentials over correctly (the service reports the user as IIS APPPOOL\ASP.NET 4.0). This is not what I want to happen.
If I change the above code to use a WebClient instead, the credentials of the user are passed correctly:
WebClient c = new WebClient
{
UseDefaultCredentials = true
};
c.DownloadStringAsync(new Uri("http://localhost/some/endpoint/"));
With the above code, the service reports the user as the user who made the request to the web application.
What am I doing wrong with the HttpClient implementation that is causing it to not pass the credentials correctly (or is it a bug with the HttpClient)?
The reason I want to use the HttpClient is that it has an async API that works well with Tasks, whereas the WebClient's asyc API needs to be handled with events.
You can configure HttpClient to automatically pass credentials like this:
var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
I was also having this same problem. I developed a synchronous solution thanks to the research done by #tpeczek in the following SO article: Unable to authenticate to ASP.NET Web Api service with HttpClient
My solution uses a WebClient, which as you correctly noted passes the credentials without issue. The reason HttpClient doesn't work is because of Windows security disabling the ability to create new threads under an impersonated account (see SO article above.) HttpClient creates new threads via the Task Factory thus causing the error. WebClient on the other hand, runs synchronously on the same thread thereby bypassing the rule and forwarding its credentials.
Although the code works, the downside is that it will not work async.
var wi = (System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity;
var wic = wi.Impersonate();
try
{
var data = JsonConvert.SerializeObject(new
{
Property1 = 1,
Property2 = "blah"
});
using (var client = new WebClient { UseDefaultCredentials = true })
{
client.Headers.Add(HttpRequestHeader.ContentType, "application/json; charset=utf-8");
client.UploadData("http://url/api/controller", "POST", Encoding.UTF8.GetBytes(data));
}
}
catch (Exception exc)
{
// handle exception
}
finally
{
wic.Undo();
}
Note: Requires NuGet package: Newtonsoft.Json, which is the same JSON serializer WebAPI uses.
What you are trying to do is get NTLM to forward the identity on to the next server, which it cannot do - it can only do impersonation which only gives you access to local resources. It won't let you cross a machine boundary. Kerberos authentication supports delegation (what you need) by using tickets, and the ticket can be forwarded on when all servers and applications in the chain are correctly configured and Kerberos is set up correctly on the domain.
So, in short you need to switch from using NTLM to Kerberos.
For more on Windows Authentication options available to you and how they work start at:
http://msdn.microsoft.com/en-us/library/ff647076.aspx
OK, so thanks to all of the contributors above. I am using .NET 4.6 and we also had the same issue. I spent time debugging System.Net.Http, specifically the HttpClientHandler, and found the following:
if (ExecutionContext.IsFlowSuppressed())
{
IWebProxy webProxy = (IWebProxy) null;
if (this.useProxy)
webProxy = this.proxy ?? WebRequest.DefaultWebProxy;
if (this.UseDefaultCredentials || this.Credentials != null || webProxy != null && webProxy.Credentials != null)
this.SafeCaptureIdenity(state);
}
So after assessing that the ExecutionContext.IsFlowSuppressed() might have been the culprit, I wrapped our Impersonation code as follows:
using (((WindowsIdentity)ExecutionContext.Current.Identity).Impersonate())
using (System.Threading.ExecutionContext.SuppressFlow())
{
// HttpClient code goes here!
}
The code inside of SafeCaptureIdenity (not my spelling mistake), grabs WindowsIdentity.Current() which is our impersonated identity. This is being picked up because we are now suppressing flow. Because of the using/dispose this is reset after invocation.
It now seems to work for us, phew!
In .NET Core, I managed to get a System.Net.Http.HttpClient with UseDefaultCredentials = true to pass through the authenticated user's Windows credentials to a back end service by using WindowsIdentity.RunImpersonated.
HttpClient client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true } );
HttpResponseMessage response = null;
if (identity is WindowsIdentity windowsIdentity)
{
await WindowsIdentity.RunImpersonated(windowsIdentity.AccessToken, async () =>
{
var request = new HttpRequestMessage(HttpMethod.Get, url)
response = await client.SendAsync(request);
});
}
It worked for me after I set up a user with internet access in the Windows service.
In my code:
HttpClientHandler handler = new HttpClientHandler();
handler.Proxy = System.Net.WebRequest.DefaultWebProxy;
handler.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
.....
HttpClient httpClient = new HttpClient(handler)
....
Ok so I took Joshoun code and made it generic. I am not sure if I should implement singleton pattern on SynchronousPost class. Maybe someone more knowledgeble can help.
Implementation
//I assume you have your own concrete type. In my case I have am using code first with a class called FileCategory
FileCategory x = new FileCategory { CategoryName = "Some Bs"};
SynchronousPost<FileCategory>test= new SynchronousPost<FileCategory>();
test.PostEntity(x, "/api/ApiFileCategories");
Generic Class here. You can pass any type
public class SynchronousPost<T>where T :class
{
public SynchronousPost()
{
Client = new WebClient { UseDefaultCredentials = true };
}
public void PostEntity(T PostThis,string ApiControllerName)//The ApiController name should be "/api/MyName/"
{
//this just determines the root url.
Client.BaseAddress = string.Format(
(
System.Web.HttpContext.Current.Request.Url.Port != 80) ? "{0}://{1}:{2}" : "{0}://{1}",
System.Web.HttpContext.Current.Request.Url.Scheme,
System.Web.HttpContext.Current.Request.Url.Host,
System.Web.HttpContext.Current.Request.Url.Port
);
Client.Headers.Add(HttpRequestHeader.ContentType, "application/json;charset=utf-8");
Client.UploadData(
ApiControllerName, "Post",
Encoding.UTF8.GetBytes
(
JsonConvert.SerializeObject(PostThis)
)
);
}
private WebClient Client { get; set; }
}
My Api classs looks like this, if you are curious
public class ApiFileCategoriesController : ApiBaseController
{
public ApiFileCategoriesController(IMshIntranetUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
public IEnumerable<FileCategory> GetFiles()
{
return UnitOfWork.FileCategories.GetAll().OrderBy(x=>x.CategoryName);
}
public FileCategory GetFile(int id)
{
return UnitOfWork.FileCategories.GetById(id);
}
//Post api/ApileFileCategories
public HttpResponseMessage Post(FileCategory fileCategory)
{
UnitOfWork.FileCategories.Add(fileCategory);
UnitOfWork.Commit();
return new HttpResponseMessage();
}
}
I am using ninject, and repo pattern with unit of work. Anyways, the generic class above really helps.
Set identity's impersonation to true and validateIntegratedModeConfiguration to false in web.config
<configuration>
<system.web>
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>
<identity impersonate="true"/>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" ></validation>
</system.webServer>
</configuration>
string url = "https://www..com";
System.Windows.Forms.WebBrowser webBrowser = new System.Windows.Forms.WebBrowser();
this.Controls.Add(webBrowser);
webBrowser.ScriptErrorsSuppressed = true;
webBrowser.Navigate(new Uri(url));
var webRequest = WebRequest.Create(url);
webRequest.Headers["Authorization"] = "Basic" + Convert.ToBase64String(Encoding.Default.GetBytes(Program.username + ";" + Program.password));
webRequest.Method = "POST";
I've got the following code:
public void StartDataRequest()
{
WebRequest.RegisterPrefix("https://", System.Net.Browser.WebRequestCreator.ClientHttp);
WebClient myService = new WebClient
{
AllowReadStreamBuffering = true,
UseDefaultCredentials = false,
Credentials = new NetworkCredential("username", "password")
};
myService.UseDefaultCredentials = false;
myService.OpenReadCompleted += this.RequestCompleted;
myService.OpenReadAsync(new Uri("Url"));
}
public void RequestCompleted(object sender, System.Net.OpenReadCompletedEventArgs e)
{
// ...
}
Now this works perfectly for, say, Twitter. But when I try to do it with another https service I get a security error.
This is probably because the website I try to connect too does not have a crossdomain.xml. Is there a way to bypass this? Or does the file really needs to be there?
Thanks.
Yes the server must have either a ClientAccessPolicy.xml file or a crossdomain.xml file. The only way I know of to get around this is to make you own server side service to act as a proxy.
I have a console application to download a file from a SharePoint site. The sharepoint site uses claims based authentication.
This code throws a 403 Forbidden exception. The specified Network credential has full access to the site, and is able to download the same file from a browser.
WebClient webClient = new WebClient();
webClient.Credentials = new NetworkCredential(username,Password,domain);
byte[] fileData = webClient.DownloadData(urlOfAFile);
FileStream file = File.Create(localPath);
file.Write(fileData, 0, fileData.Length);
Any idea how to fix this?
Maybe a bit late, but adding the right request header before making the request solves the problem:
webClient.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
I also encounter this issue, and below is my research:
{
ClientContext m_clientContext = new ClientContext(strSiteUrl);
m_clientContext.ExecutingWebRequest += new EventHandler<WebRequestEventArgs>(ctx_MixedAuthRequest);
m_clientContext.AuthenticationMode = ClientAuthenticationMode.Default;
m_clientContext.Credentials = new NetworkCredential(uname, pwd);
Web m_currentWeb = m_clientContext.Web;
m_clientContext.Load(m_currentWeb);
m_clientContext.ExecuteQuery();
}
private void ctx_MixedAuthRequest(object sender, WebRequestEventArgs e)
{
try
{
//Add the header that tells SharePoint to use Windows authentication.
e.WebRequestExecutor.RequestHeaders.Add(
"X-FORMS_BASED_AUTH_ACCEPTED", "f");
}
catch (Exception ex)
{
MessageBox.Show("Error setting authentication header: " + ex.Message);
}
}
here is the article: https://msdn.microsoft.com/en-us/library/office/hh124553(v=office.14).aspx