We use directory browsing on a specific section of our website, but our users don't really like the default ASP.NET
directory browsing. To be honest, we don't particularly care for it either.
I came across mvolo's custom directory browsing module and I attempted to use it. However, I discovered that if I have it enabled in my root web.config, it allows directory browsing on all folders without a default page (as you would expect). If I set enabled="false" in the root, it throws an HttpException, which is being caught by my generic error page, but every request is causing the exception, like when the page requested has additional images to request during the load.
As I believe (and I could be wrong), the default directory browsing module only checks for the enabled attribute if there is no default folder and you aren't requesting a specific file (for example, mysite.com/images/ versus mysite.com/images/logo.gif).
I have reconstructed the functionality of the custom module, but I am unable to figure out how to limit the module to only fully execute in situations where directory browsing would be necessary if enabled -- and not for every single request. Here is a chunk of code from the module:
public void Init(HttpApplication app)
{
app.PreRequestHandlerExecute += new EventHandler(this.OnPreRequestHandlerExecute);
}
public void OnPreRequestHandlerExecute(object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
config = (DirectoryListingConfigSection)WebConfigurationManager.GetSection("directoryBrowsing", context.Request.Path);
if (this.config == null)
{
throw new Exception("Missing <directoryBrowsing> configuration section.");
}
/* I only want to check this if it's necessary, not for things
like mysite.com/images/logo.gif or mysite.com/about/history.aspx
-- those shouldn't give a 403 error */
if (!config.Enabled)
{
context.Response.Status = "403 Forbidden";
}
/* The rest of the code goes below, and should only process
if Directory Browsing is necessary and enabled */
}
Modules are executed on every request that goes through the ASP.Net, there is no way to restrict calls to module based on type of request.
You need to built checks into your module's code to only handle requests that are of interest of that module.
Depending on the stage you should have access to most of information about request. During PreRequestHandlerExecute you have all possible information about incoming request, including Url, headers and related session state if present.
Related
Background
I've created a working bot in C# but I'm failing to expand it to be a multi-tenant bot. I have created multiple bots in the Microsoft portal using this technique to identify themselves from the messaging endpoint:
https://example.com/api/messages/bot1
https://example.com/api/messages/bot2
https://example.com/api/messages/bot3
I can grab the LastSegment from the URL while in the MessagesController and store it in PrivateConversationData so I know which bot is talking in the current conversation. I intended use this stored 'bot id' in order to retrieve the Microsoft AppId & Password from the web.config (the bot's credentials are stored as a series of custom entries and not the standard appSettings as that only works for a single bot).
Credentials Problem
The authentication works well (nearly) as described here except when using async code with .ConfigureAwait(false) I can't get the HttpContext.Current as it becomes null when running on a different thread. This means I can't get the authenticated user's credentials either by looking them up in the web.config or by calling GetCredentialsFromClaims() since I've lost the authenticated user. If I use .ConfigureAwait(true) I just get deadlocks all over the place.
I have the credentials in the web.config but they are stored per bot and I need the 'bot id' from the URL above in order to get the credentials.
Question
The crux of the problem is: I need the URL to get the 'bot id' and I need the 'bot id' to get the credentials from the web.config but I can never reliably get access to the URL once I've passed a .ConfigureAwait(false) in the code. On the flip side, I can't get the 'bot id' from the PrivateConversationData since I need the bot's credentials in order to load it. A bit chicken and egg :-(
If anyone has any ideas of what I may be doing wrong or has an alternative approach to know which 'bot id' is currently executing I'd very much appreciate it.
Thanks
Please find below given the sample code.
public class StartUp {
public void Configuration(IAppBuilder app) {
var builder = new ContainerBuilder();
//Note: Initialize / register the Metadata Service that can bring the tenant details from the corresponding store
builder.RegisterType<TenantMetadataService>().As<ITenantMetadataService>();
//Note: This helps you in accessing the TenantMetadata from any constructor going forward after the below registry
builder.Register(ti => TenantMetadata.GetTenantMetadataFromRequest()).InstancePerRequest();
//TODO: Register the various services / controllers etc which may require the tenant details here
}
}
public class TenantMetadata {
public Guid TenantId { get;set; }
public Uri TenantUrl { get;set; }
public string TenantName { get;set; }
public static TenantMetadata GetTenantMetadataFromRequest() {
var context = HttpContext.Current;
//TODO: If you have any header like TenantId coming from the request, you can read and use it
var tenantIdFromRequestHeader = "";
//TODO: There will be a lazy cache that keeps building the data as new tenant's login or use the application
if(TenantCache.Contains(...))return TenantCache[Key];
//TODO: Do a look-up from the above step and then construct the metadata
var tenantMetadata = metadataSvc.GetTenantMetadata(...);
//TODO: If the data match does not happen from the Step2, build the cache and then return the value.
TenantCache.Add(key,tenantMetadata);
return tenantMetadata;
}
}
Note
The above code snippet uses the various service placeholders, cache and the other methods which will require to be used based on the designed application services. If you wish not to cache the tenant metadata, if it may contain some sensitive data, you can remove the caching implementation parts.
This implementation can be spread across all your web facing portals like your Web UI, Web Api and WebJobs etc so that it is same across all apps and it is easy to test and consume.
HTH.
The main goal: I am creating a website (ASP.NET MVC 5) where I need to add some additional Authorization and redirection logic: For example, a user can only view page C after completing pages A and B. If they have completed page A and not B, and try to access page C, they will be redirected to page B.
After a good amount of research, my plan is to create a custom AuthorizationAttribute , and override OnAuthorization(). I want to do this responsibly, so I looked at the [source code][1] for this method, and want to only add logic to it, not take it away. I've started by copying that code over to my own subclass (which I include at the end of this post):
The problem is there are two elements here that I apparently cannot access:
MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache
CacheValidateHandler
The first is in System.Web.Mvc.Properties , and the error I get is that 'MvcResources is inaccessible due to its protection level'. All of the help I've seen online regarding this advises that the programmer change the access modifier for their class, but I can't since I didn't write this class: it's system code.
The second (CacheValidateHandler) 'does not exist in the current context'. It's a method in my parent class (AuthorizeAttribute), but it's private.
So is there something I'm missing? Does my subclass have to be in a special location (right now it's in a folder called Helpers) or do I have to do something different with namespaces? I'm still pretty new to C#. How can I safely override OnAuthorize if I'm not even able to repeat what the parent method does?
namespace MyApp.Helpers
{
public class MyAppAuth : AuthorizeAttribute
{
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
{
// If a child action cache block is active, we need to fail immediately, even if authorization
// would have succeeded. The reason is that there's no way to hook a callback to rerun
// authorization before the fragment is served from the cache, so we can't guarantee that this
// filter will be re-run on subsequent requests.
throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
}
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
if (AuthorizeCore(filterContext.HttpContext))
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
HandleUnauthorizedRequest(filterContext);
}
}
}
}
I believe I have a solution, but I would really love if someone with more experience could comment if what I'm doing is in any way unsafe.
For the first issue, I simply replaced MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache with a string, since this is one of the possible overloads of InvalidOperationException.
For the second issue, I copy-pasted the parent's private CacheValidateHandler() method:
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
Here's the deal: I have a site where multiple people will be sharing the same account and should each be able to be on a page that uploads files and keeps a list of the files they've uploaded that session. The controller for the file uploading page looks like
public class FileUploadController : Controller
{
// ...
private List<ThreePartKey> uploadedFiles = new List<ThreePartKey> ();
public ActionResult Index ( )
{
// ...
}
[HttpPost]
public ActionResult Index (HttpPostedFileBase file)
{
// ...
if (!errorOccured)
{
uploadedFiles.Add(new ThreePartKey { orgname = selectedOrgName, catname = selectedCatName, filename = fileNameNoExtension, fullfilepath = newFileUrlPathAndName });
}
// ...
}
and the problem is that uploadedFiles keeps getting re-initialized whenever [HttpPost] public ActionResult Index (HttpPostedFileBase file) is called, meaning the user's list of uploaded files only shows the last uploaded one. So I instead tried
private static List<ThreePartKey> uploadedFiles = new List<ThreePartKey> ();
and that screwed up everything because all the signed-in users are sharing the same list.
Is there any easy way to do what I'm trying to do?
Controllers are instantiated and destroyed on every request. If you want to persist information in a webserver it is strongly advised to use a permanent backing store such as a database.
You can use static state in ASP.NET applications (WebForms, MVC, OWIN, etc) however this is only recommended for caching for performance. It cannot be relied upon because static state is only local to the current AppDomain in the current Application Pool (w3wp.exe instance) - if your website is run in multiple pools or appdomains, or if your application is restarted (or killed due to inactivity) then the stored state is lost.
On option is to provide a 'session' code/id with each request. When user first connects to your site, they are given a session-code (I use 'code' to indicate it has nothing to do with what we would normally call 'session').
Every link has that session-code as part of the url and every post includes the session-code. Then your upload cache can be:
private static ILookup<int, ThreePartKey> uploadedFiles;
(or dictionary if you prefer)
private static IDictionary<int, IList<ThreePartKey>> uploadedFiles;
Depends on the size of the rest of your site if this is workable or not - in most cases probably not as described... but could be managed, eg use the IP address as the 'code' or if you're using AnglurJS or single page application.
As pointed out, any static/singleton cache will still be lost if the app pool is reset, eg via the inactivity timeout setting in IIS.
Another option is to persist the files in subfolders based on the user's IP address.
You've only stipulated that they all use the same login, not how the files are stored etc, so maybe this would work for you.
Let us consider a sample website application and running in local host.say for example www.asdf.com . when ever the user hitting the url in browser
Can it can be captured by inheriting ihttp handler or ihttp module to our class
If the url has been hitted by changing www.asdf.com?t=value is it possible to take that value.
In java this concept is used as servelet filters . Is there any thing like that in dotnet
waiting for your responses
Modules and Handlers do two different things.
Modules plug into the application and request lifecycles and respond to any number of events along the way to affect some bit of functionality to each request. Usually, it for stuff like security, logging, compression, etc. For example, FormsAuthenticationModule responds to the AuthorizeRequest (amongst others) event during each request, where it checks to see if there is an authentication ticket, validate it, and then indicate to the current context whether or not the user is authenticated (and who).
Handlers are designed to wait for a request to certain paths or extensions and do something useful. For example, requests to .ASPX files are handled by a Page handler, which parses and executes an ASPX and its associated codebehind (if there is one).
Both Modules and Handlers have access to the HttpContext object, which allows them to inspect and in many cases manipulate the current application, the current request and response, the user, etc. So yes, either can access the query string values provided during a request.
I believe modules are most analogous to a servlet filters.
This module looks for the t query string and echoes it at the beginning of every request.
public class MyModule : IHttpModule {
public String ModuleName {
get { return "MyModule"; }
}
public void Init(HttpApplication application) {
application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
}
private void Application_BeginRequest(Object source, EventArgs e) {
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write(string.Format("The value of \"t\" is {0}", context.Request.QueryString["t"]);
}
public void Dispose()
{
}
}
Is there a way to create a protected download link which is random, expiry, requires a password and pointing to a specific file in C# that is associated with IIS 7.0?
Several random links can link to the same file.
Built-in codes or perhaps 3rd party libraries?
For example, http://www.example.com/<some random gibberish>/<md5 of file>/file.jpg
One way to do this would be to use GUIDs. GUIDs are designed not to collide, and that design also leads to a difficulty in guessing valid GUIDs. I'm sure someone out there will tell me that this is not very secure! Well, you are also protecting with a password. It is pretty easy to generate a GUID in C#.
I guess what you need is firstly a way of ingesting the files that you want to protect in this way, and secondly a handler that will respond to requests in a given path and inspect the GUID in the path to determine if it's valid.
You'd then need a database back end to maintain lists of GUIDs corresponding to URLs, the password (preferably crypted) and the expiry date. The handler would inspect the entry for the requested URL/GUID to see if the link has expired, then prompt the user (could do this via a web form easily enough) for the password and check this against the crypted password stored in the database.
To generate a GUID, you want:
System.Guid.NewGuid().ToString()
To create a module that is called before every request (for IIS7) you can add an entry to your web.config like so:
<modules>
<add name="MyDownloadModule" type="Example.MyDownloadModule, Example"/>
</modules>
where MyDownloadModule is the class containing your handler, in the namespace Example.
Inside that class you then need to implement the IHttpModule interface, in particular overriding the methods:
public string ModuleName {
get { return "MyDownloadModule"; }
}
public void Init(HttpApplication app) {
// Add an event handle which is called at the beginning of each request
app.BeginRequest += new EventHandler(this.AppBeginRequest);
}
//
// Our event handler for the BeginRequest event
//
private void AppBeginRequest(Object source, EventArgs e)
{
HttpRequest request = app.Context.Request;
//
// Is this a file download?
//
if (request.AppRelativeCurrentExecutionFilePath == "~/downloads") // or whatever
{
// this is where you work your GUID inspecting magic
}
}
Going about it this way means this will be called for every request to the server, which may not be what you want.
You could always create your own HttpHandler, and then implement your own proprietary expiration/validation code.
Something like:
http://www.example.com/download?token={your_token}
It would then be a trivial matter to have the handler intercept the request and grab the file from disk, and deliver it to the client if the ?token querystring value is correct.
For more information on the IHttpHandler interface, see MSDN http://msdn.microsoft.com/en-us/library/system.web.ihttphandler.aspx