I have a file directory that can be access directly from the URL
e.g.
http://website.com/DirectoryName/folder/downloadpdf.pdf
I need to control whom can access the files so I have created a ASHX handler so I check permissions of the user.
however, when I access http://website.com/DirectoryName/ the handler doesnt get called.
I believe its something to do with my config, I have
<location path="DirectoryName">
<system.webServer>
<handlers>
<add name="PSHandler" path="DirectoryName/*" verb="*" type="ProjectName.PSHandler"/>
</handlers>
</system.webServer>
</location>
Different way - IIS 6?
<location path="DirectoryName">
<system.web>
<httpHandlers>
<add path="DirectoryName/*" verb="*" type="ProjectName.PSHandler, PSHandler"/>
</httpHandlers>
</system.web>
</location>
Is there something wrong with the above?
the ashx file
<%# WebHandler Language="VB" Class="PSHandler" %>
Imports System
Imports System.Web
Public Class PSHandler : Implements IHttpHandler
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
context.Response.ContentType = "text/plain"
context.Response.Write("Hello World")
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
FYI this is by running locally using IIS Express
Thanks
I think you are very close. One thing is you will need to write file to response.
public class PSHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string fileName = Path.GetFileName(context.Request.Path);
string filePath = context.Server.MapPath("~/DirectoryName/" + fileName);
// Check permission of user
context.Response.ContentType = "application/pdf";
context.Response.WriteFile(filePath);
}
public bool IsReusable
{
get { return false; }
}
}
<system.webServer>
<handlers>
<add name="PS" path="DirectoryName/*" verb="*"
type="ProjectName.PSHandler, ProjectName"
preCondition="integratedMode"/>
</handlers>
</system.webServer>
Update
First of all, replace httpHandlers with authorization tag.
<location path="DirectoryName">
<system.web>
<authorization>
<deny users="*"/>
</authorization>
</system.web>
</location>
Then add handler inside application's web.config.
<system.webServer>
<handlers>
<add name="PS" path="DirectoryName/*" verb="*"
type="ProjectName.PSHandler, ProjectName"
preCondition="integratedMode"/>
</handlers>
</system.webServer>
Then copy and paste the following PSHandler code.
Public Class PSHandler
Implements IHttpHandler
Public Sub ProcessRequest(context As HttpContext)
Dim fileName As String = Path.GetFileName(context.Request.Path)
Dim filePath As String = context.Server.MapPath(Convert.ToString("~/DirectoryName/") & fileName)
' Check permission of user. If permission denied, display 404 -
' context.Server.Transfer("~/404.aspx")
context.Response.ContentType = "application/pdf"
context.Response.WriteFile(filePath)
End Sub
Public ReadOnly Property IsReusable() As Boolean
Get
Return False
End Get
End Property
End Class
Reqeust URL should be like this -
http://www.yourwebsite.com/DirectoryName/downloadpdf.pdf
OR
http://localhost:xxxx/DirectoryName/downloadpdf.pdf
Please make sure downloadpdf.pdf file exists in DirectoryName folder.
If it is still doesn't work, please create a new Web Application, and test the above code. I have tested and it works fine.
Related
In a migration from WebForms to MVC, some .aspx pages remain. Authentication for these is currently file-based and happens through Web.config. MVC authentication happens by adding an AuthorizeAttribute to GlobalFilters.Filters and to the controllers/actions as needed.
The Web.config authentication currently looks like this:
<authentication mode="Forms">
<forms loginUrl="~/SignIn.aspx" protection="All" path="/" timeout="10080" />
</authentication>
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
<location path="SomePage.aspx">
<system.web>
<authorization>
<allow roles="Administrator, BasicUser" />
<deny users="*" />
</authorization>
</system.web>
</location>
I would however like to move the WebForms authentication away from Web.config and into an MVC filter that performs a check using a method call. I have been unable to find a single example of this. Is this even possible, and are there any undesired implications of doing it?
I am aware that .aspx files are not handled by the MVC hooks by default.
I am looking for a way to grab all .aspx files regardless of what their code-behind's base class is.
In Global.asax.cs,
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// Since MVC does not handle authentication for .aspx pages, do this here.
AuthController.AuthenticateAspxPage(this.Context);
}
and in AuthController, for some type UserRole,
public const string ErrorRedirectUrl = "~/Auth/Error";
private static readonly IDictionary<string, UserRole[]> _aspxAccessRoles =
new Dictionary<string, UserRole[]>()
{
{ "~/SomePage.aspx", new UserRole[] { UserRole.Administrator,
UserRole.BasicUser } },
...
};
and then,
[NonAction]
public static void AuthenticateAspxPage(HttpContext context)
{
string ext = context.Request.CurrentExecutionFilePathExtension;
string aspxPage = context.Request.AppRelativeCurrentExecutionFilePath;
ClaimsPrincipal principal = context.User as ClaimsPrincipal;
if ((ext == ".aspx" || ext == ".ashx") && !HasAspxAccess(aspxPage, principal))
{
context.Response.Redirect(ErrorRedirectUrl);
}
}
[NonAction]
public static bool HasAspxAccess(string aspxPage, ClaimsPrincipal principal)
{
if (principal == null || principal.Claims == null)
{
return false;
}
string[] userRoles = principal.Claims
.Where(claim => claim.Type == ClaimTypes.Role)
.Select(claim => claim.Value)
.ToArray();
UserRole[] accessRoles;
return _aspxAccessRoles.TryGetValue(aspxPage, out accessRoles)
&& accessRoles.Any(role => userRoles.Contains(role.ToString()));
}
Having a code-blind moment.
ASP.NET 4.0.
Web.config:
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Forms">
<forms name="DataViewer" loginUrl="login.aspx">
<credentials passwordFormat="Clear">
<user name="devuser" password="test" />
</credentials>
</forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
</system.web>
and a login control:
<asp:Login ID="login" runat="server" />
If I enter a username and password, and click Login, it hangs.
If I break, I can see in the call stack that login.AuthenticateUsingMembershipProvider() is in the middle of calling SqlMembershipProvider.ValidateUser(). There is no database defined or involved in this project at all, and I haven't specified that SqlMembershipProvider should be used.
So my question is, what membership provider should I use to get ASP.NET to use the usernames and passwords in the <credentials> element of web.config?
I'm amazed that considering how the framework designers went to the trouble of defining a <credentials /> element that they didn't implement any code to consume it.
I found a sort-of-working implementation of this here which I have fixed up and included below. All other members of MembershipProvider throw NotImplementedException.
using System.Configuration;
using System.Web.Configuration;
using System.Web.Security;
public class WebConfigMembershipProvider : MembershipProvider
{
private FormsAuthenticationUserCollection _users = null;
private FormsAuthPasswordFormat _passwordFormat;
public override void Initialize(string name,
System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(name, config);
_passwordFormat = getPasswordFormat();
}
public override bool ValidateUser(string username, string password)
{
var user = getUsers()[username];
if (user == null) return false;
if (_passwordFormat == FormsAuthPasswordFormat.Clear)
{
if (user.Password == password)
{
return true;
}
}
else
{
if (user.Password == FormsAuthentication.HashPasswordForStoringInConfigFile(password,
_passwordFormat.ToString()))
{
return true;
}
}
return false;
}
protected FormsAuthenticationUserCollection getUsers()
{
if (_users == null)
{
AuthenticationSection section = getAuthenticationSection();
FormsAuthenticationCredentials creds = section.Forms.Credentials;
_users = section.Forms.Credentials.Users;
}
return _users;
}
protected AuthenticationSection getAuthenticationSection()
{
Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
return (AuthenticationSection)config.GetSection("system.web/authentication");
}
protected FormsAuthPasswordFormat getPasswordFormat()
{
return getAuthenticationSection().Forms.Credentials.PasswordFormat;
}
}
You are going to need to write your own provider for this. It should be relatively straightforward to take the sample ReadOnlyXmlMembershipProvider in the MSDN documentation and change it to read users and credentials from web.config, instead of an external XML file.
I'm not sure if you have tried but....
The FormsAuthentication.Authenticate is in charge to do that for you (although it is deprecated now because the recommended behavior is to use the Membership object)
From MSDN:
The Authenticate method verifies user credentials that are stored in the credentials section of the application configuration file. Alternatively, you can use ASP.NET membership to store user credentials and call the ValidateUser to verify the credentials.
You can also remove the membership providers (because even when you do not declare them on your web.config, they are inherited from the machine.config file)
<membership>
<providers>
<remove name="AspNetSqlMembershipProvider"/>
</providers>
</membership>
Try using this method. I hope it helps. http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.login.onauthenticate.aspx
I create an IIS Module that appends text to the page before it loads. When I go to the URL, this works perfect the first time the page loads. On subsequent loads, however, the text is never appended.
Any thoughts on how to remedy this?
== CODE ==
Here's my web.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.webServer>
<modules>
<add name="MIModule" type="MI.MyModule, MI" />
</modules>
<caching enabled="false" enableKernelCache="false" />
</system.webServer>
</configuration>
Some module code:
public void context_PreRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpRequest request = app.Context.Request;
string pageContent = app.Response.Output.ToString();
string useragent = "HI!<br />" + pageContent + "<hr />" ;
try
{
_current.Response.Output.Write(useragent);
}
catch
{
}
}
and the rest of the code:
private HttpContext _current = null;
#region IHttpModule Members
public void Dispose()
{
throw new Exception("Not implemented");
}
public void Init(HttpApplication context)
{
_current = context.Context;
context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
}
#endregion
Is the _current variable actually HttpContext.Current? Is it a static field in your module? When/how is it initialized? My guess is that the empty catch clause gobbles up all errors, and following that thought, you most probably get a null reference on _current. Try removing the try/catch to learn more on what's wrong with your code
I have a custom handler like this,
public class Handler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}
public bool IsReusable
{
get
{
return false;
}
}
}
Why does this not work?
<system.diagnostics>
<sources>
<source name="System.Web" switchValue="All" propagateActivity="true">
<listeners>
<add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="c:\logs\Traces_Documents.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
When that is in the web.config it does nothing when I call the handler. No logging etc.
I have previously used the same trace listener on the System.ServiceModel namespace with a WCF service, and that worked.
Do you have tracing enabled?
<system.web>
<trace enabled="true" writeToDiagnosticsTrace="true" />
</system.web>
Also, what version of the framework / IIS are you using?
See the DnrTV episode w/ Steve Smith which has good information on Asp.Net Tracing.
I've wrote a simple handler:
public class ImageHandler : IHttpHandler, IRequiresSessionState
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
byte[] imgData = context.Session["Data"] as byte[];
if (imgData != null)
{
context.Response.CacheControl = "no-cache";
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.ContentType = "image/png";
context.Response.BinaryWrite(imgData);
context.Response.Flush();
}
}
}
And setup the web.config:
<system.web>
<httpHandlers>
<add verb="GET" path="image.png" type="TestWeb.Handlers.ImageHandler, TestWeb" />
</httpHandlers>
</system.web>
<system.webServer>
<handlers>
<add name="Image" verb="GET" path="image.png" type="TestWeb.Handlers.ImageHandler, TestWeb" />
</handlers>
</system.webServer>
If I run the code allowing VS start a new IIS service and open a new tab it reaches the breakpoint on the handler.
If I set don't open a page. Wait for request from an external application it never reaches the handler.
It is not just the breakpoint, no code from the handler executes when I run the website configured on IIS. It only works if I start from VS.
What did I miss when configuring IIS7 ?
I had to switch the Application Pool to Integrated mode, it was using classic.
And I had to remove the handler configuration from <system.web> because it was giving me error 500.23.
HTTP Error 500.23 - Internal Server
Error An ASP.NET setting has been
detected that does not apply in
Integrated managed pipeline mode.
you need to attach to the asp.net worker process. go to tools/attach to process and choose the w3p process.