I have a very simple asp.net core 2.0 application and what it looks like a very simple problem, but I can't find the solution.
My application store diferent private images for users like photo, copy of their id...those images of course have to be private and only the web application can show them in certain pages. For example the administrator can see them when browsing the users in the admin panel, or a user can see his images...
The files are in a folder "users/1/photo.jpg" or "users/243/id.jpg". Of course those folders have to be private and you can't browse them.
How can I do so when I use a image tag I can see the image:
<img src="???">
Without showing the real path and also preventing anyone to access that file but the pages I want.
Thanks.
UPDATE
First of all, thanks to Mark Redman and T.S., you helped a lot.
Finally what I'm doing is to have the sensible images outside the StaticFiles public folder and the non-sensible ones in the wwwroot folder.
PART 1. SENSIBLE IMAGES
For the sensible images I'm using a IActionResult to return the file, but after I encrypt the file name. This is just an example...
public IActionResult ViewUser(int id)
{
var model = new Model();
....
model.EncryptedId = _protector.Protect(id.ToString("D6"));
return View(model);
}
This way I can return the encrypted id to retrieve the image I want without publishing the real id.
In my View:
<img src="/home/GetImage?id=#Model.EncryptedId" />
And the GetImage would look like this:
public IActionResult GetImage(string encryptedId)
{
var decryptedId = _protector.Unprotect(encryptedId);
var file = Path.Combine(_hostingEnvironment.ContentRootPath, "MyPrivateFiles", decryptedId + ".jpg");
return PhysicalFile(file, "image/jpeg");
}
This way, as far as I understand:
- I'm protecting my private files by not storing in a public folder such as wwwroot, so no one can download them directly.
- Also no one can get the id of an existing user and try to call my Action GetImage?id=232 because I have encrypted that id.
Other protection level I can have is only authorize certain users to access the GetImage Action, for example allowing users only to get their images or allowing administrators to download any.
PART 2. NON SENSIBLE IMAGES
For the non sensible images (such as user public photos) I'm storing them in the wwwroot because I need them to be public.
Using the asp-append-version="true" I can cache images, which is a very good improvement of this Asp.Net Core.
The only thing left for me would be to obfuscate those image names so I'm not showing "domain.com/users/1234.jpg" and show, for example, "domain.com/users/sdfjknkjSD2.jpg".
I don't know how to do this WITHOUT LOSING the advantage of the caching.
Is there a way to do this?
Thanks in advance.
It is best practice to not store your images in the web folders.
here is a very basic action, something like this...
[HttpGet]
public FileResult GetImage(string userId, string id)
{
return File($"{YourRootPath}users/{userId}/{id}.jpg"), "image/jpeg");
}
<img src="/YourController/GetImage/?userId=243&id=123"/>
for .net core you might want to do this;
public async Task<IActionResult> GetImage(string userId, string id)
{
Stream stream = await [get stream here__]
if(stream == null)
return NotFound();
return File(fileStream, "image/jpeg", $"{id}.jpg");
}
One solution i can suggest to this problem is, creating a folder using GUID id and storing the image of the user in that folder. So this happens for each user so now while showing using img tag, only the app would be able to render the correct image as the path is derived from the db while hackers would find it almost impossible to guess the guid id which essentially is a folder name and required for guessing the image and rendering it on webpage.
Related
The majority of my views are regular <action>.cshtml files in the normal /Views/<Controller> folder hierarchy. These are source controlled in git and deployed in the usual "rip and replace" way.
However I also use Razor for rendering templates to create HTML emails, and the email .cshtml templates are specific to each client. I therefore want to be able to load and render them from outside the Application Root folder, so the client-specific customisations are not lost during deployment.
I have successfully created and registered an implementation of the IViewLocationExpander interface and this works for folders inside the Application Root:
public class EmailViewLocationExpander : IViewLocationExpander
{
protected readonly String _TemplateFolder;
public EmailViewLocationExpander( String TemplateFolder )
{
_TemplateFolder = TemplateFolder.Trim('/');
}
public void PopulateValues( ViewLocationExpanderContext context )
{
}
public IEnumerable<string> ExpandViewLocations( ViewLocationExpanderContext context, IEnumerable<string> viewLocations )
{
var result = new List<String>( viewLocations );
result.Add( $"/{ _TemplateFolder }/Email/{{0}}.cshtml" );
result.Add( $"/{ _TemplateFolder }/Shared/{{0}}.cshtml" );
return result;
}
}
It does not seem to work for paths other than Application Root relative, so e.g. /../Templates doesn't seem to work.
I also currently rely on having a custom _ViewStart.cshtml for my email templates, and reading the Mvc source code leads me to think that my only option is to implement a custom IFileProvider to reference the physical file system outside the current Application Root - is that right and can anyone help me with an example if it is?
That seems right to me. The ViewLocationExpander deals with paths relative to the wwwroot folder so it's not going to be useful for specify paths to files outside of that.
Here is a pretty good article on implementing an IFileProvider. The articles demonstrates creating and IFileProvider for accessing views stored in a database but reading them from the file system will be even easier. So it's a great outline of what to consider. https://www.mikesdotnetting.com/article/301/loading-asp-net-core-mvc-views-from-a-database-or-other-location
One thing I noted is that the way the way the IFileProvider works is pretty cool. You register your custom implementation as an option to the RazorViewEngine like below and when the ViewEngine needs to get a file, it asks each FileProvider, in order, for that file until one returns it.
public void ConfigureServices(IServiceCollection services)
{
string customLocation = #"c:\mylocation\"
// Add framework services.
services.AddMvc();
services.Configure<RazorViewEngineOptions>(opts =>
opts.FileProviders.Add(
new MyCustomFileProvider(customLocation)
)
);
}
So in the end, you basically just implement your FileProvider to provide files for some specific url endpoints. You can obtain them from anyplace you like.
Hey guys¡ I have one several problem. Perhaps it could be solver over there, but I can't find the solution.
I'm saving in request session the file that the user upload to the server. User's can upload .pdf, .doc, .xls and image extensions.
Until now, I only can return images, but no pdf or doc files.
The problem appears when in my download controller, I have to set the headers for the file and it depends on the file I get from the session. I dont know how especific that. Its the code:
#RequestMapping(value = "/api/previsualizarFacturaOriginal/{idFacturaOriginal}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public static ResponseEntity<byte[]> getImageFacturaOriginal(
final #PathVariable("idFacturaOriginal") String idFacturaOriginal,
final HttpServletRequest request) {
try {
//get object from the session
final byte[] file = (byte[]) request.getSession().getAttribute(
Naming.SESSION_PDF_FACTURA_ORIGINAL + idFacturaOriginal);
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
return new ResponseEntity<byte[]>(file, headers, HttpStatus.OK);
} catch (final Exception e) {
return new ResponseEntity<byte[]>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
A couple of notes here:
Be careful when you store large blobs in the session. I don't know the specific context of your application so take this warning with a grain of salt but if it's a public facing application a large number of uploads could quickly overwhelm your available JVM memory (unless you have carefully configured your app to store the session data to disk and the uploads sizes are small). Just food for thought. https://www.owasp.org/index.php/Unrestricted_File_Upload Another caveat to using sessions is that you now will need to worry about session stickiness or session replication if you want to have multiple application servers. Although dated you can read about other mechanisms here: What is the best place for storing uploaded images, SQL database or disk file system?
I am assuming that you used Spring's MultipartFile upload which allows you to get the content type of the file pretty easily: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html. If that's the case then when the file is uploaded and stored in the session you could literally put the whole MultiPartFile in the session OR store the content type separately in the same session and just retrieve it at the time of download.
I'm using https://github.com/filipw/AspNetWebApi-OutputCache to add easy caching to my web-api project and I have an action that look something like this:
[HttpGet]
[CacheOutput(ClientTimeSpan = 86400, ServerTimeSpan = 86400)]
public List<Things> GetThings()
{
return service.GetThings();
}
Now things are a combination of a list of things that apply to everybody along with user-defined things that are created by a user and accessible only to that user. So I want the cache here to be tied to a specific user. I don't want user Bob getting a list of things that included things that are specific to Sally. So I created my own key generator, inheriting from DefaultCacheKeyGenerator that will append the user id:
public override string MakeCacheKey(System.Web.Http.Controllers.HttpActionContext context, System.Net.Http.Headers.MediaTypeHeaderValue mediaType, bool excludeQueryString = false)
{
var key = base.MakeCacheKey(context, mediaType, excludeQueryString);
return string.Format("{0}:{1}", key, userService.CurrentUser.UserID);
}
The UserID here is ultimately pulled from the user authorization cookie.
This seems to work fine.
However, I have another action that will let the user save their custom thing and obviously when I POST here I want to invalidate the cache, so it looks something like this:
[HttpPost]
[InvalidateCacheOutput("GetThings")]
public void SaveUserThing(UserThingModel thing)
{
service.Save(thing);
}
The problem (or rather the inefficiency) here is that from my understanding this will flush everything under this control and GetThings (the base key for all caches) which will include the cache for every user. This means if Bob saves a new thing, I'm going to force Sally to have to get a whole new list of things, even though her list won't have changed.
Is there an easy way around this? I suspect the problem lies in CacheOutputConfiguration.MakeBaseCacheKey, but there doesn't seem to be a mechanism to override that functionality to have it build a base key from controller, action and userId.
I could probably just grab the source from GitHub and adapt to suit my needs, but I wanted to be sure I wasn't a) missing something obvious and b) barking up the wrong tree.
I'm integrating a JavaScript library into an ASP.NET MVC3 web app. The library assumes it will be installed next to the page that references it, and so it uses document-relative URLs to find its components.
For example, the default directory layout looks like
container-page.html
jslibrary/
library.js
images/
icon.png
extensions/
extension.js
extension-icon.png
However, I want to reference the library from the view in /Home/edit. I install the library in the default Scripts\jslibrary\ When I reference the library in the view in Views\Home\edit.cshtml, the library's document-relative links like
images/icon.png
end up as requests to
http://localhost/Home/images/icon.png
which results in a File Not Found (404) error. How do I construct a route to look for
{anyControllerName}/images/{anyRemainingPathInfo}
and serve up
http://localhost/Scripts/jslibrary/images/{anyRemainingPathInfo}
?
(full disclosure: I'm still on IIS 6 in Production, and not much chance of going to IIS7 any time soon, so if this is better done at the IIS level, please account for IIS6. Thanks!)
You could create a controller for handling you redirect logic - for example an "Images"controller. Register a global route in your Global.asax file, using the pattern (more on this type of pattern here:
routes.MapRoute(
"Images", // Route name
"{xyz}/{controller}/{path}", // URL with parameters
new {controller = "Images", action = "Index", path= UrlParameter.Optional} // Parameter defaults);
In your controller:
public ActionResult Index(string path)
{
//format path, parse request segments, or do other work needed to Id file to return...
return base.File(path, "image/jpeg"); //once you have the path pointing to the right place...
}
Not sure if this solution will work for you, wish I could come up with something more elegant. Best of Luck!
Short of rewriting the library and having it check for the appropriate directory the only solution I can think of is to include the views, library and supporting files in a directory structure that the library can access. This of course would break MVC's convention over configuration way of finding views, so you would have to write a custom override of the way Razor looks for views, which is not too complex to do, but you might be making life more difficult for yourself down the road depending on your application. Your call which is the lesser of the two evils :) (I'd go for fixing the library)
Make a help function
#functions{
public string AbsoluteUrl(string relativeContentPath)
{
Uri contextUri = HttpContext.Current.Request.Url;
var baseUri = string.Format("{0}://{1}{2}", contextUri.Scheme,
contextUri.Host, contextUri.Port == 80 ? string.Empty : ":" + contextUri.Port);
return string.Format("{0}{1}", baseUri, VirtualPathUtility.ToAbsolute(relativeContentPath));
}
}
Calling
#AbsoluteUrl("~/Images/myImage.jpg") <!-- gives the full path like: http://localhost:54334/Images/myImage.jpg -->
This example are from
https://dejanvasic.wordpress.com/2013/03/26/generating-full-content-url-in-mvc/
I have an application where each user can choose a custom layout. The layouts can be different and it's not just css styles but html as well.
I know that mvc would cache the layout, but having so many layouts I doubt it would fit in cache. So what would it be better to save templates in DB or on the disk?
FYI: DB that I'm using is MongoDB.
I would save the layouts on disk because at the moment I don't see any advantage in a database (unless you do). But one thing that is worth mentioning is that you can create a class derived from OutputCacheAttribute and have your saved result depend on the layout you're using.
Does the layout depend on user? You could use the VaryByCustom property to have it vary by user.
EDIT
Are your users allowed to change layouts dinamically? If yes, you should also have a guid associated to your users change it each time the layouts change so you return on your VaryByCustom method:
return string.Format("User-{0}-{1}", user.Id, user.LayoutUpdateGuid);
See the meaning of this? This way, when a user changes the layouts, they will see their pages updated immediately.
How to apply the VaryByCustom attribute in your situation
In your action method, you may use:
[OutputCache(Duration = 3600, VaryByCustom = "UserLayouts")]
public ActionResult Details(string param)
{
// Returning the view
}
Then, in your VaryByCustom method in your Global.asax.cs file:
protected override string VaryByCustom(string custom)
{
switch (custom)
{
case "UserLayouts":
//// Here you fetch your user details so you can return a unique
//// string for each user and "publishing cycle"
//// Also, I strongly suggest you cache this user object and expire it
//// whenever the user is changed (e.g. when the LayoutUpdateGuid is
//// changed) so you achieve maximum speed and not defeat the purpose
//// of using output cache.
return string.Format("User-{0}-{1}", user.Id, user.LayoutUpdateGuid);
break;
}
}
The missing piece
The missing piece here is that you need to store a value that I called LayoutUpdateGuid (I'm sure you'll find a better name) and change that value whenever a user changes his layouts => this will lead to a different string being returned by the VaryByCustom(string) method in the Global.asasx.cs which in turn will force your action method to run again and return the result with the updated layout.
Makes sense to you?
Note: I can't test the specific code I wrote here, but I am sure (apart from typos) it is correct.