The Problem
On another project we have been talking about how we could allow a person
upload a file to the Web application, and be able to secure the file so that
someone couldn't type the file URL directly in the browser and be able to
download the file.
Several methods were discussed of providing a secure file system for our
application:
Database Storage
Storing the file in a database in a binary field, but this method could cost
money for folks
hosting with an ISP that charges for database storage, and it also impacts network
traffic. Basically this solution entails storing the file in the database and
attaching some security information in the database about the file; this could be
user roles that
are allowed to download the file. You can accomplish this by uploading the
file to the database; when someone goes to access the file you would
authenticate them, check the roles that can access the file, then check the user
to see if they have permissions to access the file. Once you're done with the
security checks create an ADO stream
and send the file information to their client.
The database method meets the requirement of providing secure method of
downloading files and prevents someone from being able to access the file directly
via
typing in the URL into the browser. But as mentioned there are several drawbacks
to this method.
File Storage
Another method would be storing the file in a folder on the Web server. If
the file were stored in the Web application directory structure you could secure
it using NT ACLs. While this method works for
an intranet where you have administrative access to the machine, it does not
work well if you are hosting a site with an ISP. An ISP may not be able to
provide you with the level of security you need for your application.
Another file storage method was storing the files in a folder outside of the
Web
application's directory structure and streaming the file to the browser. This
would be accomplished much in the same way as the database solution, where you
could store security information in a table, but the actual file resides on
the Web server. You would do your security checks in your code and send the
file to the user once they authenticated for access to the file. This method
would accomplish restricting the file for downloading directly from typing
in the URL of the file since the code is handling the file stream. Again the
drawback to this method is you have to rely on an administrator to configure the
folders that will reside outside of the Web directory structure.
Another method was to store the file within the directory structure and use
the web.config file to restrict access to the directory in the following manner:
<location path="SecureDirectory">
<system.web>
<authorization>
<deny users="*" />
</authorization>
</system.web>
</location>
This method will secure any requests that are
being processed by ASP.Net, the problem is it will not secure files that are not
being processed by ASP.Net; for example, pdf, doc, xls, and other files you wish
to secure. A way around this is to change the settings in
IIS so all file extensions are processed by ASP.Net. Again, in the ISP case they
may
not want to do this because it can have some performance implications.
One Solution
So how do you provide access to files and
ensure they cannot be accessed directly by typing in a URL? The following blocks
of code will cover one method of doing this using a combination of all the
methods described above. This can be done entirely via code.
First, select a location for storing your files.
As in the example web.config file mentioned previously, we'll select the "SecureDirectory"
folder off
of the Web root. We will keep the web.config modification to restrict access to
this folder by unauthorized groups. We then create a database table to store
security information for our file.
|
FILE_NAME
|
ACCESS_ROLES
|
|
myfile.doc
|
admin;managers
|
This table will contain the names of the files that are uploaded to our
secure directory and the security roles that can access the file. Notice the
actual file is not stored in the database just the associated security information.
The File Upload Code
Now that you have your table defined to store security information for your
file, we need to create methods for uploading and
downloading the documents from the server. We will create a webform with a file
browse dialog to browse our local system and upload it the server. In your
Webform.aspx file add the following:
<INPUT id="cmdBrowse" type="file" runat="server" size="50" NAME="cmdBrowse">
<asp:LinkButton id="cmdUpload" runat="server" Cssclass="CommandButton">
Upload File
</asp:LinkButton>
Then in our code behind page, Webform.aspx.vb, we need to handle the file
upload. The following code will take the file that is being uploaded, save it
into our secure directory as defined in the web.config file, and add the
extension "resources" to the file so it will secure the file from a directly
typed URL. You could use any extension like .vb, .acsx, .config, .resources, .resx or any file type
that will be processed by the .Net handler.
Private Sub cmdUpload_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles cmdUpload.Click
SecureFileUpload()
End Sub
Public Sub SecureFileUpload()
Dim strFileName As String
Dim strFileNamePath As String
strFileName = System.IO.Path.GetFileName(cmdBrowse.PostedFile.FileName)
'now save the file as an resources file.
strFileNamePath = Request.MapPath("SecureDirectory") & "\" & strFileName & ".resources"
If File.Exists(strFileNamePath) Then
File.Delete(strFileNamePath)
End If
cmdBrowse.PostedFile.SaveAs(strFileNamePath)
End Sub
So now if a person tries to go to the file directly by typing in the URL they
will be greeted by a login prompt and an eventual 401.2 status message of
"Access is denied"
Denied download from directly entering the file URL.
Downloading the File
So now that we have the file on our Web server and it can't be downloaded by
browsing to the file URL, how are we supposed to get the file to the people who
are supposed to get it?
First, you need to pass the file that you want to download and check it
against your database to see if they have permissions on the file. If they have
permissions for the file, then proceed with the download. You can write any security
check you
want, you may want to run a stored procedure to check to see if your user is a
member of a certain role for your portal. Since the security mechanism will vary
depending on the application, we will call a CheckSecurity method that returns either
true or false depending on whether or not the person has access to the file as
defined by the table earlier in this article.
If CheckSecurity(filename, userole) Then
SecureFileDownload(filename)
Else
'change the http response to access denied or some other error.
End If
After checking the permissions in the database, if the user has access to the
file we then call the SecureFileDownload method which accepts the file path as
the parameter, maps the file to the physical directory on the server, then sends
the download to the client without the resources extension allowing them to download
the file.:
Public Sub SecureFileDownload(ByVal inFile As String)
Dim strFileNamePath As String
strFileNamePath = Request.MapPath("SecureDirectory") & "\" & inFile
Dim myFile As FileInfo = New FileInfo(strFileNamePath)
Response.Clear()
'now we send the file header minus the resources extension.
Response.AddHeader("Content-Disposition", "attachment; filename=" & _
Replace(myFile.Name, ".resources", ""))
Response.AddHeader("Content-Length", myFile.Length.ToString())
Response.ContentType = "application/octet-stream"
Response.WriteFile(myFile.FullName)
Response.End()
End Sub
Download dialog for the file.
The user will be presented with a download dialog and is now able to save the
file to their local system.
As discussed there are several methods you can do to secure your file
downloads, the method described in this article is ideally suited for those who
do not have access to the administrative functions of the Web server.
By:
Patrick Santry, Microsoft MVP (ASP/ASP.NET), MCSE, MCSA, MCP+SB,
contributor of "Windows 2003 Server - The Complete Reference", Co-Author of
"Administering IIS 5.0" both books published by McGraw-Hill. In addition,
Patrick has written several articles and co-authored several books on Microsoft
certification. You can view his personal website at
http://www.santry.com/.