ASHX sends HttpWebRequest with Impersonation On, which works to URLs on same server but not on Remote ArcGIS server - asp.net

This is a complicated issue, so bear with me.
Scenario: using a ASHX proxy to relay request to an ArcGIS server.
Trying to use ASP.NET impersonation, so that the logged in ASP.NET user credentials are used by the proxy, when sending request to the ArcGIS server.
Issue: the proxy request to ArcGIS server is refused 401, even though I know the impersonated account (sean.ryan-B + sean.ryan) does have access.
There are 4 machines:
1. machine hosting proxy page. I am logged in as: sean.ryan-B
2. a test machine. I am logged in as sean.ryan-B
3. my laptop. I am logged in as sean.ryan
4. the arcgis server.
All 4 machines are on the same domain.
web.config:
<authentication mode="Windows"/>
<identity impersonate="true" /> <!-- userName="EUROPE\sean.ryan-B" password="xxx" -->
<authorization>
<deny users="?"/>
</authorization>
Test-1. Opening a test page, in same web app as proxy, via the proxy:
http://myHost.com/sean/ProxyAsp.Net/ArcGisProxy.ashx?http://myHost.com/sean/ProxyAsp.Net
[ok on all boxes 1-3]
This looks OK - the impersonation seems look OK,
since with impersonation OFF: WindowsIdentity.GetCurrent().Name = the AppPool account
with impersonation ON: WindowsIdentity.GetCurrent().Name = EUROPE\sean.ryan or EUROPE\sean.ryan-B
Test-2. opening an image that is hosted on the same IIS (but a different site), via the proxy:
http://myHost.com/sean/ProxyAsp.Net/ArcGisProxy.ashx?http://myHost.com:10400/sites/CaSPER/SiteAssets/CaSPER.jpg
[ok on boxes 1-3]
Test-3. opening the ArcGIS map URL, via the proxy:
http://myHost.com/sean/ProxyAsp.Net/ArcGisProxy.ashx?http://mapserver1.com/ArcGIS/rest/services/Global/2D_BaseMap_SurfaceGeology/MapServer?f=json&callback=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback
[fails on boxes 2,3 but succeeds on the proxy host (box 1)!]
code for the ASHX code-behind:
public partial class ArcGisProxy : IHttpHandler, IReadOnlySessionState //ASHX implements IReadOnlySessionState in order to be able to read from session
{
public void ProcessRequest(HttpContext context)
{
try
{
HttpResponse response = context.Response;
// Get the URL requested by the client (take the entire querystring at once
// to handle the case of the URL itself containing querystring parameters)
string uri = context.Request.Url.Query;
uri = uri.Substring(1); //the Substring(1) is to skip the ?, in order to get the request URL.
System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)WebRequest.Create(uri);
{
req.Credentials = CredentialCache.DefaultCredentials; //this works on local box, with -B account. this is the account the web browser is running under (rather than the account logged into CaSPER with, as ASHX has separate server session).
req.ImpersonationLevel = TokenImpersonationLevel.Impersonation;
}
//to turn off caching: req.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
req.Method = context.Request.HttpMethod;
req.ServicePoint.Expect100Continue = false;
req.Referer = context.Request.Headers["referer"];
// Set body of request for POST requests
req.Method = "GET";
// Send the request to the server
System.Net.WebResponse serverResponse = null;
try
{
serverResponse = req.GetResponse();
}
catch (System.Net.WebException webExc)
{
//logger.Log(GetMyUrl(), webExc, context.Request);
response.StatusCode = 500;
response.StatusDescription = webExc.Status.ToString();
response.Write(webExc.ToString());
response.Write(webExc.Response);
response.Write("Username = " + context.User.Identity.Name + " " + context.User.Identity.IsAuthenticated + " " + context.User.Identity.AuthenticationType);
response.End();
return;
}
// Set up the response to the client
....
......
response.End();
}
catch (Exception ex)
{
throw;
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
note: the following changes, meant proxy request to the map server DOES succeed:
a) set the identity in the web.config to explicitly set username, password to the sean.ryan-B account:
-OR-
b) set the App Pool account to be sean.ryan-B and turn OFF impersonation in the web.config file.
however these changes are not acceptable for Production.
The problem seems to be that:
- ASP.NET impersonation works well enough for test page + image hosted on same IIS (tests 1 and 2)
but NOT well enough for the map server.
as far as I know, the ArcGIS map server is using Negotiate, and then Kerberos authentication.
With WireShark, I monitored a successful proxy request, and found:
after 401, proxy sends GET with AUTH using SPNEGO (Kerberos)
Has anyone had similar issue with ArcGIS proxy ?
My theory is, that the impersonation on box 1 'works better', because browser is running on same box as the proxy.
Could the ArcGIS Server (or the IIS site it is using) be restricted to prevent accepting impersonation ?
Any suggestions welcome ...
p.s. had a hard time getting this post through - had to format most of it as code, as s-o is detecting it as source code !

Related

SQL Server Report Server (SSRS) via WCF: HTTP request is unauthorized with client authentication scheme 'Ntlm'

I have a .Net Core 3.1 application which is trying to connect to a SQL Server Report Server via WCF, in order to programmatically generate reports on demand.
But the program is not able to authenticate against the Report Server.
Here is the relevant program code:
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
binding.MaxReceivedMessageSize = 10485760; //10MB limit
// Create the execution service SOAP Client
var rsExec = new ReportExecutionServiceSoapClient(
binding,
new EndpointAddress("http://my-ssrs/ReportServer")
);
// Setup access credentials.
var clientCredentials = new NetworkCredential(
"MyReportServerUserName",
"MyReportServerPassword",
"."
);
if (rsExec.ClientCredentials != null)
{
rsExec.ClientCredentials.Windows.AllowedImpersonationLevel =
System.Security.Principal.TokenImpersonationLevel.Impersonation;
rsExec.ClientCredentials.Windows.ClientCredential = clientCredentials;
}
// ************************************************
// Get following Exception when next line executes.
// ************************************************
await rsExec.LoadReportAsync(null, "/path-to/my-report", null);
When the last line ("rsExec.LoadReportAsync") is executed, I get the following exception:
The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'NTLM'.
The Report Server is on the same Windows Domain.
After some research, I've tried changing the ClientCredentialType = HttpClientCredentialType.Windows but this generated a different exception, as follows:
The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'NTLM'.
Does anyone have any suggestions about what I might try?
Had the same problem. Solved it by additionally setting the proxy credential type:
binding.Security.Transport.ProxyCredentialType = System.ServiceModel.HttpProxyCredentialType.Ntlm;

when I use asmx service and SSRS report service I am getting "The request failed with http status 401: unauthorised"

I was trying to call report related service (asmx) from my asp.net web application by running locally.
Then an exception happened saying. The request failed with http status401:unauthorised.
In my analysis I understood the issue caused due to below code
SSRSWebService.ReportingService2005 rs = new SSRSWebService.ReportingService2005();
rs.Credentials = new MyReportServerCredentials().NetworkCredentials;
and
Uri reportUri = new Uri(ConfigurationManager.AppSettings["ReportServerManagement.ReportingService2005"]);
this.rptViewer.ServerReport.ReportServerCredentials = new MyReportServerCredentials();
In my detailed analysis I understood that the issue was because of the credential set up in serviceObject.credential OR ServerReport.ReportServerCredentials was wrong. This can be rectified in two different way either by setting credential to default with below code
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;//"rs" is report object
Or locate below code and set up proper authenticated user credential in the code
public WindowsIdentity ImpersonationUser
{
get
{
// Use the default Windows user. Credentials will be
// provided by the NetworkCredentials property.
return null;
}
}
public ICredentials NetworkCredentials
{
get
{
// Read the user information from the Web.config file.
// By reading the information on demand instead of
// storing it, the credentials will not be stored in
// session, reducing the vulnerable surface area to the
// Web.config file, which can be secured with an ACL.
// User name
string userName =
<<AccurateUserName;>>
// Password
string password =
<<AccuratePassword;>>
// Domain
string domain = <<AccurateDomainName;>>
return new NetworkCredential(userName, password, domain);
}
}
In order to check whether which user has the access, we need to type service url ending with asmx (http:/MyServiceHostedServer/MyService.asmx) in a web browser. It will prompt a user name and password . Give our username as :Domain\Username and password.If we are able to see wsdl xml file then that user has the access.

Can IIS require SSL client certificates without mapping them to a windows user?

I want to be able to map SSL client certificates to ASP.NET Identity users. I would like IIS to do as much of the work as possible (negotiating the client certificate and perhaps validating that it is signed by a trusted CA), but I don't want IIS to map the certificate to a Windows user. The client certificate is passed through to ASP.NET, where it is inspected and mapped to an ASP.NET Identity user, which is turned into a ClaimsPrincipal.
So far, the only way I have been able to get IIS to pass the client certificate through to ASP.NET is to enable iisClientCertificateMappingAuthentication and set up a many-to-one mapping to a Windows account (which is then never used for anything else.) Is there any way to get IIS to negotiate and pass the certificate through without this configuration step?
You do not have to use the iisClientCertificateMappingAuthentication. The client certificate is accessible in the HttpContext.
var clientCert = HttpContext.Request.ClientCertificate;
Either you enable RequireClientCertificate on the complete site or use a separate login-with-clientcertificate page.
Below is one way of doing this in ASP.NET MVC. Hopefully you can use parts of it to fit your exact situation.
First make sure you are allowed to set the SslFlags in web.config by turning on feature delegation.
Make site accept (but not require) Client Certificates
Set path to login-with-clientcertificate-page where client certificates will be required. In this case a User controller with a CertificateSignin action.
Create a login controller (pseudo-code)
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
[AllowAnonymous()]
public ActionResult CertificateSignIn()
{
//Get certificate
var clientCert = HttpContext.Request.ClientCertificate;
//Validate certificate
if (!clientCert.IsPresent || !clientCert.IsValid)
{
ViewBag.LoginFailedMessage = "The client certificate was not present or did not pass validation";
return View("Index");
}
//Call your "custom" ClientCertificate --> User mapping method.
string userId;
bool myCertificateMappingParsingResult = Helper.MyCertificateMapping(clientCert, out userId);
if (!myCertificateMappingParsingResult)
{
ViewBag.LoginFailedMessage = "Your client certificate did not map correctly";
}
else
{
//Use custom Membersip provider. Password is not needed!
if (Membership.ValidateUser(userId, null))
{
//Create authentication ticket
FormsAuthentication.SetAuthCookie(userId, false);
Response.Redirect("~/");
}
else
{
ViewBag.LoginFailedMessage = "Login failed!";
}
}
return View("Index");
}

DotNetOpenAuth Failing to work on Live Server

I worked on a sample application integrating OpenID into ASP.NET Web Forms. It works fine when hosted locally on my machine. However, when I uploaded the application to a live server, it started giving "Login Failed".
You can try a sample here: http://samples.bhaidar.net/openidsso
Any ideas?
Here is the source code that fails to process the OpenID response:
private void HandleOpenIdProviderResponse()
{
// Define a new instance of OpenIdRelyingParty class
using (var openid = new OpenIdRelyingParty())
{
// Get authentication response from OpenId Provider Create IAuthenticationResponse instance to be used
// to retreive the response from OP
var response = openid.GetResponse();
// No authentication request was sent
if (response == null) return;
switch (response.Status)
{
// If user was authenticated
case AuthenticationStatus.Authenticated:
// This is where you would look for any OpenID extension responses included
// in the authentication assertion.
var fetchResponse = response.GetExtension<FetchResponse>();
// Store the "Queried Fields"
Session["FetchResponse"] = fetchResponse;
// Use FormsAuthentication to tell ASP.NET that the user is now logged in,
// with the OpenID Claimed Identifier as their username.
FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, false);
break;
// User has cancelled the OpenID Dance
case AuthenticationStatus.Canceled:
this.loginCanceledLabel.Visible = true;
break;
// Authentication failed
case AuthenticationStatus.Failed:
this.loginFailedLabel.Visible = true;
break;
}
}
As Andrew suggested, check the exception. In my case, my production server's time & date were off and it wouldn't authenticate because the ticket expired.
Turn on logging on your live server and inspect them for additional diagnostics. It's most likely a firewall or permissions problem on your server that prevents outbound HTTP requests.
You may also find it useful to look at the IAuthenticationResponse.Exception property when an authentication fails for clues.

Why my httpwebrequest post to myhandler.ashx is rejected with status code 401

I've already written an HTTPHandler that gets POSTed from a ColdFusion page and it works successfully; now, I am trying to write a web application in ASP.NET so I can post a form to the .ashx handler from an .aspx page.
Application Trace (trace.axd) shows the following as my last 3 entries:
2 8/14/2009 1:53:56 PM /Default.aspx 200 GET View Details
3 8/14/2009 1:54:04 PM /Default.aspx 200 POST View Details
4 8/14/2009 1:54:13 PM /UploadHandler.ashx 401 POST View Details
I have a breakpoint in my .ashx file but it is never reached (I guess because of the 401 status code). Here is the snippet of code from the default.aspx trying to POST to the handler:
protected void UploadHandlerButton_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
try
{
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] data = encoding.GetBytes(BuildFormData());
string baseAddress = "http://" + Environment.MachineName;
string pathInfo = Page.ResolveUrl("UploadHandler.ashx");
string URI = baseAddress + pathInfo;
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(URI);
myRequest.Method = "POST";
myRequest.ContentType = "application/x-www-form-urlencoded";
myRequest.ContentLength = data.Length;
Stream newStream = myRequest.GetRequestStream();
newStream.Write(data, 0, data.Length);
newStream.Close();
}
catch (Exception someError)
{
LogText("FAILURE: " + someError.Message);
}
}
}
Here is a snippet of code from the UploadHandler.ashx file (but this doesn't appear to be reached):
public void ProcessRequest(HttpContext context)
{
string returnURL = context.Request.ServerVariables["HTTP_REFERER"];
string message;
message = UploadFile(context);
StringBuilder msgReturn = new StringBuilder(returnURL);
msgReturn.Append("?n=");
msgReturn.Append(HttpUtility.UrlEncode(TRIMrecNumAssigned));
msgReturn.Append("&m=");
msgReturn.Append(HttpUtility.UrlEncode(message));
context.Response.Redirect(msgReturn.ToString());
}
Both default.aspx and UploadHandler.ashx are in the root of a virtual directory on my localhost; the directory security is currently set to "Anonymous access" CHECKED and "Integrated Windows authentication" CHECKED.
When I click the "View Details" link on the trace.axd display, I see all the data in the Forms collection that I expect to see and hope to process but this 401 seems to be stopping everything. I could post the code for my little function called BuildFormData() if useful.
EDIT: Revised handler as follows (has had no effect; same error occurs):
public void ProcessRequest(HttpContext context)
{
//-----------------------------------------------------------------------------------------
// the remainder of this block is alternative to the .Redirect and is useful for debugging.
context.Response.ContentType = "text/html";
//context.Response.Write(TRIMrecNumAssigned);
//context.Response.Write("<p>");
//context.Response.Write(msgReturn);
context.Response.Write("<H1>Trim - Kerberos Prototype for ColdFusion consuming pages</h1>");
HttpContext.Current.Trace.IsEnabled = true;
HttpContext.Current.Trace.Write(null);
HttpContext.Current.Trace.Write("-------");
HttpContext.Current.Trace.Write(context.Request.Form["txtTrimRecordType"]);
HttpContext.Current.Trace.Write(GetUserInfo());
HttpContext.Current.Trace.Write("-------");
HttpContext.Current.Trace.Write(null);
using (Html32TextWriter htw = new Html32TextWriter(context.Response.Output))
{
typeof(TraceContext)
.GetMethod("Render", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(HttpContext.Current.Trace, new object[] { htw });
}
}
Have you tried turning off Integrated Windows Auth and just leaving anonymous checked? Does it make a difference?
Your answer: "I think it made things worse because now I cannot even browse to default.aspx. I get this: HTTP 401.3 - Access denied by ACL on resource Internet Information Services"
My response: This is actually a good thing. This means we're getting closer to what is going on. If you're getting that error message and the only thing you have enabled is anonymous authentication via IIS, that means that the ASP.NET impersonation user doesn't have NTFS permissions on the files in question.
I'm not sure if you are on XP or Win 2k3, but now you want to check and make sure that either the ASPNET (XP) or Network Service (Win 2k3) users have at least read access on the files in question. Make sure that user has at least that level of access and then let me know how it goes.
Update: I don't know why I didn't think of this before. You may need to set credentials on your HttpWebRequest. To use the credentials of the current user, try adding this to your request.
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(URI);
myRequest.Credentials = CredentialCache.DefaultCredentials;
If you need to add different credentials you can try Network Credentials
There's a good explanation of credentials here.
Hope this helps.
Looking at your ProcessRequest(), you do the following:
string returnURL = context.Request.ServerVariables["HTTP_REFERER"];
Based on how you are calling it with HttpWebRequest, this variable will be null. Then when you create your msgReturn, it will look something like this:
?n=XXX%m=YYY
When you redirect to this URL, it will probably not be found which is what is returning the 401.

Resources