Webservice pdf file works in Debug but not after publish - asp.net

We have a frankenstein webservice that I've been fixing a problem with pdf production and everything is working as expected in Debug but when I publish to the test IIS7 server it runs but I don't get the PDF.
The website callse this function and passes in the claimId
Public Function QServices_QAudit_GetWorkersCompCoverPage(ByVal claimId As Long) As Byte()
Dim sess As General.Session = Session.Item("sessionObject")
Dim pdfDocument As System.IO.MemoryStream
If Not AuthorizeUser(sess, Context, "qaudit") Then Return Nothing
pdfDocument = QAudit.QAuditData.GetWorkersCompCoverPage(claimId, sess)
If pdfDocument Is Nothing Then Return Nothing
Dim retValue As Byte()
ReDim retValue(0 To pdfDocument.Length - 1)
Dim i As Long
pdfDocument.Seek(0, IO.SeekOrigin.Begin)
For i = 0 To pdfDocument.Length - 1
retValue(i) = pdfDocument.ReadByte()
Next
Return retValue
End Function
Which in turn calls a function which reads in a pdf form, populates the fields saves it to the file system then converts it to a memory stream and returns it. Its converted to a byte stream and sent back to the website. Everything works great in debug but after publish, the pdf never gets saved. I'm thinking a permissions issue on either the folder that holds the blank pdf form or the folder that we write the completed form to, but I've given every permission I can think of including Network Services. IUSR, IIS_USRS and Authenticated Users but no change in the result. I've set the Application pool to Network Service, Local Service, Local System and the default and still no joy.
Any ideas on if there is another permission set I'm missing or something else in IIS7 that is preventing the file reading/writing?

Finally getting back to this one but learned it was a combination of IIS 7 loosing folder permissions every time we published the website and the app pool permissions of the webservice since it was running on a different level of .net. Basically we moved the pdf logic from the webservice to the website appcode folder and gave the local iis user permissions on this folder (through IIS, not through windows explorer) and everything is stable.

Related

Why app is throwing error in test environment but working fine in local machine using ASP.NET Web Forms and MSAL?

Scenario:
Using the application, customer can access the emails in within the application and decide to save it. I have used Exchange Web Service Managed API for this. Things were working fine until IT implemented Multi factor Authentication. Now the program cannot access the mailbox based on the username and password.
Solution
In order to overcome this issue I integrated MSAL(Microsoft Authentication Library) to acquire the Access token in order to use it with the EWS Managed API to access the emails. I have registered an application in the Azure and used the clientID and TenentID. It works perfectly fine when testing locally. But throws an error when deploying in dev/test environnment. I am not sure what to do. Could you please help me with this?
Private Function GetDelegatedExService() As ExchangeService
If Session("EwsOAuthToken") Is Nothing Then
Dim caller As New AsyncGetToken(AddressOf GetDelegatedToken)
' Initiate the asynchronous call.
Dim result As IAsyncResult = caller.BeginInvoke(Nothing, Nothing)
' thread from any process ready to run and has a higher priority then Sleep(0) will yield the processor and let it run
Thread.Sleep(0)
' Perform additional processing here and then wait for the WaitHandle to be signaled.
result.AsyncWaitHandle.WaitOne()
' Call EndInvoke to retrieve the results.
Dim returnValue As String = caller.EndInvoke(result)
Session("EwsOAuthToken") = returnValue
End If
Dim ewsClient = New ExchangeService()
ewsClient.Url = New Uri("https://outlook.office365.com/EWS/Exchange.asmx")
Dim myToken = Session("EwsOAuthToken").ToString()
' Setup OAuthCredentials with the token
ewsClient.Credentials = New OAuthCredentials(myToken)
Return ewsClient
End Function
Private Function GetDelegatedToken() As String
Dim pcaOptions = New PublicClientApplicationOptions() With
{
.ClientId = ConfigurationManager.AppSettings("appId"),
.TenantId = ConfigurationManager.AppSettings("tenantId")
}
Dim redirectUrl = ConfigurationManager.AppSettings("EBWebURL")
Dim pca = PublicClientApplicationBuilder _
.CreateWithApplicationOptions(pcaOptions) _
.WithRedirectUri(redirectUrl) _
.Build()
' The permission scope required for EWS access
Dim token = OpenAuthPopup(pca).GetAwaiter().GetResult()
Return token
End Function
Private Async Function OpenAuthPopup(pca As PublicClientApplication) As Tasks.Task(Of String)
Dim ewsScopes = {"https://outlook.office365.com/EWS.AccessAsUser.All"}
Dim authResult = Await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync()
Return authResult.AccessToken
End Function
This is the local testing where user is able to get the login by Microsoft account prompt. They have to enter their organization account details, gets the Multi Factor Auth code in phone, adds in the prompt and then gets authenticated(it simply returns a access token).
But when the code is deployed to DEV/TEST environment. I get the following error. It is not even prompting for the Microsoft Login. And I tried searching for the given error, but not able to figure out what the problem actually is with the asp.net web app or Microsoft Auth Library.
I am not experienced with the technologies you describe in your question but I will try to give you my persepctive on the issue. From what I can understand, the code you have pasted here is supposed to run on the web-server. Testing the application locally, the client is requesting information from the web-server but the web-server needs first to aqcuire an authentication token. The modal window that appears on the screen, asking from the client to authenticate, seems to be fired by
pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync()
This code runs on the web-server and it is the web-server that shows this pop-up. The clients can only enter their credentials because the testing is taking place on the same machine where the web-server lives. In produciton environments, web app code is not allowed to open a modal window. Even if it was permitted, there would be no one there to interact with the pop-up.
I checked the MSIL library and I found that PublicClientApplication contains more authentication methods and some of them are non-interactive, such as AquireTokenByUsernamePassword. I am not sure how you can handle the code received by the user's mobile. I cannot help any further on the library since I am not familiar with it.
Another option might be to make the client-browser open a pop-up window on https://outlook.office365.com/EWS.AccessAsUser.All and somehow return the authorization token back to the web-server. You might want to take a look at this and see if it suits your needs.

What actually happens when you Stop Debugging?

I have 2 ASP.NET applications. 1 is in VB, 1 is in C#.
When the user logins with certain credentials in the VB app should be re-routed to the C# app. Likewise, certain credentials for the C# app gets re-routed to the VB app, and vice versa.
VB -> C# works. This functionality was written by a third party. (The C# application is essentially just a rewrite of our VB app, but more modern. However, the entire package isn't being rewritten).
I've tried to reverse the code so that the C# app will call a stored procedure in the DB to create a token, redirect the browser to the VB app which calls a procedure to get that token and set some Session variables.
I don't have it quite working right, one of the major issues is that that the Browser simply does not navigate off the C# login page to the VB page. If I run Profiler on the DB however, I can see that "load token" stored procedure being called. That must mean that the code is getting executed, but the browser isn't redirecting correctly, right?
More importantly, and the reason I'm posting this question however is I don't understand what's actually happening when I stop debugging my app. I set a break point immediately following the call to create that token in the DB. So I run my application, log in, trigger the break point and I can see the good data in the DB. If I immediately Stop Debugging, the load token procedure still gets called. How!?
Here's the code;
In my LoginController:
public ActionResult ValidateUser(objLogin)
{
var ds = LoginData.ValidateUser(objLogin);
string url = "someUrl/" + ds.Tables["Key"].Rows[0][0].ToString();
System.Web.HttpContext.Current.Response.Redirect(url, true);
return Json(objLogin, JsonRequestBehavior.AllowGet);
}
That redirect points to the landing page in the VB application, which parses out the key from the URL and passes that as a parameter to another DB SP... However, the browser never navigates off my login page regardless if I stop debugging or not.
Frankly, I'm not entirely sure what the return statement does; if I try to step into it it just continues on as if I hit "Play". Application resumes control and just chills at the login page. It's part of the third-party rewrite. The VB app was very old, pretty unstructured. New C# rewrite uses MVC. I'm familiar with the principles but I'm not an expert on it, especially not in .NET.
And in LoginData
public DataSet ValidateUser(Login objLogin)
{
DataSet dsData;
using (SqlCommand sqlCommand = new SqlCommand("Validate_User_Main")
{
// execute this procedure; assign results to dsData
}
string authKey = GetAuthKey(dsData.userId);
DataTable dtTemp = new DataTable("key"); //putting break point here after the key gets created but before the redirect is called in LoginController.ValidateUser
dtTemp.Columns.Add("Key");
DataDrow drTemp = dtTemp.NewRow();
drTemp[0] = authkey;
dtTemp.Rows.Add(drTemp);
dsData.Tables.Add(dtTemp);
return dsData;
}
Edit: If I close my browser window while still waiting on my breakpoint, then stop debugging that "load token" call isn't utilized. If I simply Stop Debugging but leave my browser open, it gets called. So it must be redirecting "behind the scenes", right? I don't understand...
When you stop debugging the debugger is detached. This simply means that it stops tracking the running code. The code keeps running, as you have seen, but know you can't set breakpoints, watch variable etc.

Starting Process from ASP.NET

I'm running an ASP web application that should start a Powershell script on the server. To run this Powershell script a lot of Domain rights are needed. So I run the apppool under a user that has all the rights.
But when I start the powershellscript I alway get the that the access is denied.
Has any one an idea how to solve the problem?
When I start a process as described, is the process running under the usercontext of the app pool or under the usercontext of the user which is logged in in the ASP.NET web application?
I'ver tried two methods
1.
string cmdArg = "C:\\Scripts\\test.ps1 " + username;
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(cmdArg);
pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
string test = Environment.UserName;
}
return results[0].ToString();
2.
string cmdArg = "C:\\Scripts\\test.ps1 " + username;
Process myProcess = new Process();
ProcessStartInfo myProcessStartInfo = new ProcessStartInfo("powershell.exe",cmdArg);
myProcessStartInfo.UseShellExecute = false;
myProcessStartInfo.RedirectStandardOutput = true;
myProcess.StartInfo = myProcessStartInfo;
myProcess.Start();
StreamReader myStreamReader = myProcess.StandardOutput;
myProcess.WaitForExit();
string myString = myStreamReader.ReadLine();
return myString;
Ok, you think running the Apppool with these grand permissions is not best practise.
What about puting a webservice between? The webservice is in an appdomain that is only reachable from localhost?
Update
Ok, I've written an asp.net webservice. The webservice runs in an applicationpool with all rights but is only reachable from localhost. The webservice contains the code to start the script. The ASP MVC3 webapplication is running in a applicationpool with nearly no rights.
But when the webmethod is executed I always get an error that tell me, that I haven't enought rights. I tried to set the impersonate in the webconfig false, but without success.
Does anyone know how to solve this probleme?
Update:
I've read out the current user who execute the powershell when I start it from the webservice. I says it is the user who've got all rights. But the ps throws Errors like: you can't start a method with value null.
Then I've tried to run the ps with runsas as a low level user. I get the same errors.
Then I've tried to run the ps with the same user as in the webservice and everything worked!
Is there anyone who could explain this phenomenon?
And what is the different between my code above and a runas? (same user context)
thanks a lot!
Starting a new process in a HTTP request is not great for performance and it may also be a security risk. However, before ASP.NET and other modern web servers was available the only way to serve content (besides static files) was to execute a program in a separate process.
The API for doing this called the Common Gateway Interface (CGI) and still supported by IIS. If configured correctly you can execute a program on each request.
I'm not sure that you can use CGI to execute a script but then you can create an ISAPI filter that will execute PowerShell on files having extension .ps1. This is basically how for instance php.exe is executed in a new process when a file with extension .php is requested.
Enabling executable content can and should be done on a folder-by-folder basis to limit the security risk. In general you should avoid mixing different kinds of content, ie. it should not be possible to both view a script and also execute it.
If you intention is to be able to remotely run PowerShell scripts and not much else it should also be easy to write a small HTTP server in PowerShell completely removing IIS and ASP.NET from the equation.
I suppose this merely depends on the impersonation settings, if impersonation is enabled, then the currently logged in user is used, otherwise the app pool user

asp.net 2.0 asp:FIleUpload control saving uploaded files to a different server

I'm trying to use an asp:FileUpload Control to allow users to upload files (.doc, .gif, .xls, .jpg) to a server that is outside of our DMZ and not the Web Server. We want to have the ability to look at these files for viruses, structure, etc prior to saving them into another directory that would allow access to outside users. From what I have read about this control is that it will allow for files to be uploaded to the web server. Can this control be used to upload files to a server other than the web server? If it can be done where should I look for this type of functionality or how do I force it to go to https:\servername\folder name (Where server name is not the web server)? Would I have to read the file then write it to the other server?
Thanks,
Erin
FileUoload control can only upload data to the web server. If you need to save file to a different server, you need handle the POST request, read data from the Fileupload control and save them to your UNC share.
As far I know, using the fileupload control you actually upload contents to webserver which inturn gets rendered to your client (page) when requested; I don't think you can upload files to different server other than webserver; that shouldn't happen as well. Take a look at the below URL for fileupload if you want
http://msdn.microsoft.com/en-us/library/aa479405.aspx
http://www.asp.net/data-access/tutorials/uploading-files-cs
Thanks.
This depends on your web server setting and permission granted to the application. If it is DMZ then I would assume that a very minimal permission is granted to the application. In such scenario the application will not be able to access any resource other than webserver unless an explicit permission is granted to the account running application to access the network resource (which is not recommended). However, if the nework server you are trying to save the file has ftp enabled, then you could write the bytes streamed in file upload control to the network server with authenticated ftp account that has necessary permission.
You may use the below function:
Imports System.Net
Imports System.IO
Public Function Upload(ByVal FileByte() As Byte, ByVal FileName As String, ByVal ftpUserID As String, ByVal ftpPassword As String, ByVal ftpURL As String) As Boolean
Dim retValue As Boolean = False
Try
Dim ftpFullPath As String = ftpURL + "/" + FileName
Dim ftp As FtpWebRequest = FtpWebRequest.Create(New Uri(ftpFullPath))
ftp.Credentials = New NetworkCredential(ftpUserID, ftpPassword)
ftp.KeepAlive = True
ftp.UseBinary = True
ftp.Method = WebRequestMethods.Ftp.UploadFile
Dim ftpStream As Stream = ftp.GetRequestStream()
ftpStream.Write(FileByte, 0, FileByte.Length)
ftpStream.Close()
ftpStream.Dispose()
retValue = True
Catch ex As Exception
Throw ex
End Try
Return retValue
End Function
Function Call:
Upload(FileUploadControl.FileBytes, "filename.ext" "user", "password", "ftppath")

File permissions with FileSystemObject - CScript.exe says one thing, Classic ASP says another

I have a classic ASP page - written in JScript - that's using Scripting.FileSystemObject to save files to a network share - and it's not working. ("Permission denied")
The ASP page is running under IIS using Windows authentication, with impersonation enabled.
If I run the following block of code locally via CScript.exe:
var objNet = new ActiveXObject("WScript.Network");
WScript.Echo(objNet.ComputerName);
WScript.Echo(objNet.UserName);
WScript.Echo(objNet.UserDomain);
var fso = new ActiveXObject("Scripting.FileSystemObject");
var path = "\\\\myserver\\my_share\\some_path";
if (fso.FolderExists(path)) {
WScript.Echo("Yes");
} else {
WScript.Echo("No");
}
I get the (expected) output:
MY_COMPUTER
dylan.beattie
MYDOMAIN
Yes
If I run the same code as part of a .ASP page, substituting Response.Write for WScript.Echo I get this output:
MY_COMPUTER
dylan.beattie
MYDOMAIN
No
Now - my understanding is that the WScript.Network object will retrieve the current security credentials of the thread that's actually running the code. If this is correct - then why is the same user, on the same domain, getting different results from CScript.exe vs ASP? If my ASP code is running as dylan.beattie, then why can't I see the network share? And if it's not running as dylan.beattie, why does WScript.Network think it is?
Your problem is clear. In the current implementation you have only impersonation of users and no delegation. I don't want to repeat information already written by Stephen Martin. I only want to add at least three solutions. The classical way of delegation which Stephen Martin suggests is only one way. You can read some more ways here: http://msdn.microsoft.com/en-us/library/ff647404.aspx#paght000023_delegation. I see three practical ways of you solving your problem:
Convert the impersonation token of the user to a token with delegation level of impersonation or to a new primary token. You can do this with respect of DuplicateToken or DuplicateTokenEx.
Use S4U2Self (see http://msdn.microsoft.com/en-us/magazine/cc188757.aspx and http://msdn.microsoft.com/en-us/library/ms998355.aspx) to receive a new token from the old one with respect of one simple .NET statement WindowsIdentity wi = new WindowsIdentity(identity);
You can access another server with respect of one fixed account. It can be a computer account on an account of the application pool of the IIS. It can be another fixed defined account which one will only use for access to the file system.
It is important to know which version of Windows Server you have on the server where IIS is running and which Domain Function Level you have in Active Directory for your Domain (you see this in "Active Directory Domain and Trusts" tool if you select your domain and choose "Raise Domain Functional Level"). It is also interesting to know under which account the application pool of the IIS runs.
The first and the third way will always work. The third way can be bad for your environment and for the current permission in the file system. The second one is very elegant. It allows control of which servers (file server) are accessed from IIS. This way has some restrictions and it needs some work to be done in Active Directory.
Because you use classic ASP, a small scriptable software component must be created to support your implementation.
Which way do you prefer?
UPDATED based on the question from comment: Because you use classic ASP you can not use a Win32 API directly, but you can write a small COM component in VB6 or in .NET which use APIs which you need. As an example you can use code from http://support.microsoft.com/kb/248187/en. But you should do some other things inside. So I explain now which Win32 API can help you to do everything what you need with tokens and impersonation.
First of all a small explanation about impersonation. Everything works very easy. There are always one primary token under which the process runs. To any thread another token (thread token) can be assigned. To do this one needs to have a token of a user hUserToken and call API ImpersonateLoggedOnUser(hUserToken);.
To go back to the original process token (for the current thread only) you can call RevertToSelf() function. The token of user will be received and already impersonated for you by IIS, because you so configured your Web Site. To go back to the original process token you should implement calling of the function RevertToSelf() in your custom COM component. Probably, if you need to do nothing more in the ASP page, it will be enough, but I recommend you be more careful and save current users token in a variable before operation with files. Then you make all operations with file system and at the end reassign users token back to the current thread. You can assign an impersonation token to a thread with respect of SetThreadToken(NULL,hUserToken);. To give (save) current thread token (user token in your case) you can use OpenThreadToken API. It must work.
UPDATED 2: Probably the usage of RevertToSelf() function at the end of one ASP page would be already OK for you. The corresponding C# code can be so:
Create a new Project in C# of the type "Class Library" with the name LoginAdmin. Paste the following code inside
using System;
using System.Runtime.InteropServices;
namespace LoginAdmin {
[InterfaceTypeAttribute (ComInterfaceType.InterfaceIsDual)]
public interface IUserImpersonate {
[DispId(1)]
bool RevertToSelf ();
}
internal static class NativeMethods {
[DllImport ("advapi32.dll", SetLastError = true)]
internal static extern bool RevertToSelf ();
}
[ClassInterface (ClassInterfaceType.AutoDual)]
public class UserImpersonate : IUserImpersonate {
public UserImpersonate () { }
public bool RevertToSelf () {
return NativeMethods.RevertToSelf();
}
}
}
Check in project properties in "Build" part "Register for COM interop". In "Signing" part of the project check Sign the assembly and in "Choose a strong name key file" choose <New...>, then type any filename and password (or check off "protect my key..."). At the end you should modify a line from AssemblyInfo.cs in Properties part of the project:
[assembly: ComVisible (true)]
After compiling this project you get two files, LoginAdmin.dll and LoginAdmin.tlb. The DLL is already registered on the current computer. To register if on the other computer use RegAsm.exe.
To test this COM DLL on a ASP page you can do following
<%# Language="javascript" %>
<html><body>
<% var objNet = Server.CreateObject("WScript.Network");
Response.Write("Current user: ");Response.Write(objNet.UserName);Response.Write("<br/>");
Response.Write("Current user's domain: ");Response.Write(objNet.UserDomain);Response.Write("<br/>");
var objLoginAdmin = Server.CreateObject("LoginAdmin.UserImpersonate");
var isOK = objLoginAdmin.RevertToSelf();
if (isOK)
Response.Write("RevertToSelf return true<br/>");
else
Response.Write("RevertToSelf return false<br/>");
Response.Write("One more time after RevertToSelf()<br/>");
Response.Write("Current user: ");Response.Write(objNet.UserName);Response.Write("<br/>");
Response.Write("Current user's domain: ");Response.Write(objNet.UserDomain);Response.Write("<br/>");
var fso = Server.CreateObject("Scripting.FileSystemObject");
var path = "\\\\mk01\\C\\Oleg";
if (fso.FolderExists(path)) {
Response.Write("Yes");
} else {
Response.Write("No");
}%>
</body></html>
If the account used to run the IIS application pool has access to the corresponding network share, the output will be look like following
Current user: Oleg
Current user's domain: WORKGROUP
RevertToSelf return true
One more time after RevertToSelf()
Current user: DefaultAppPool
Current user's domain: WORKGROUP
Yes
Under impersonation you can only access securable resources on the local computer you cannot access anything over the network.
On Windows when you are running as an impersonated user you are running under what is called a Network token. This token has the user's credentials for local computer access but has no credentials for remote access. So when you access the network share you are actually accessing it as the Anonymous user.
When you are running a process on your desktop (like CScript.exe) then you are running under an Interactive User token. This token has full credentials for both local and remote access, so you are able to access the network share.
In order to access remote resources while impersonating a Windows user you must use Delegation rather then Impersonation. This will involve some changes to your Active directory to allow delegation for the computer and/or the users in your domain. This can be a security risk so it should be reviewed carefully.

Resources