Secure Media

For a client I need to create a way of managing secure media (primarily PDF documents) so
their clients can access the files through the front end.

I imagine I can just create a Content Type with a Media picker field, then restrict access to those Content Types using the current Orchard fruits. Easy.

However the documents cannot be accessed publicly through the URL, unless the user is authenticated. I've done similar before on WebForm sites by using IIS file protection handlers etc; however I really would prefer an Orchard-friendly way if possible.

For those much smarter than I (not hard really), could you offer your wisdom on this topic?

Okay, so if I were to create a SecureMedia part (or field or whatever), when the ContentItem is fetched, will Orchard load this binary from the database each time? Thinking worst-case, some of the PDFs could be quite large (10-20 MB), which is a lot
to load through SQL Server. The client's site will be hosted on a shared server, also sharing it's database on a shared SQL server.

Admittedly, I don't think the site will receive high traffic for the secure files, however I believe it's important to consider these issues
just in case. If all the users logged on at the same time to download the latest 20 MB PDF report (all print ready and filled with 300 dpi stock images), the ol' SQL Server might freak out and do something stupid.

Okay cool, (for the sake of documentation) so I should be able to write a fairly simple module which allows file uploads to the App_Data folder implementing
IAppDataFolder and deliver said files using
TransmitFile.

Sweet, now I've got an idea on how it could work, it's time to get down and dirty with pen and paper to figure out the specifics. Thanks for your help guys!

Oh wow, of all places, not App_data. Please. That's absolutely crazy. Never, ever, for any reason, serve anything from app_data. That is about the most dangerous thing you could do. Just don't do it.

@AimOrchard: if you published a module that does that on the gallery, please tell me its name and I'll delete it. I might also ban you permanently from ever publishing anything on the gallery. Kidding, but barely.

Oh wow, of all places, not App_data. Please. That's absolutely crazy. Never, ever, for any reason, serve anything from app_data. That is about the most dangerous thing you could do. Just don't do it.

How would you suggest to store the documents in the file system within Orchard Bertrand? Would it be advisable to create a "Secure Media" folder in the root and simply TransmitFile the media from there? What is the best convention?

Okay cool, (for the sake of documentation) so I should be able to write a fairly simple module which allows file uploads to the App_Data folder implementing
IAppDataFolder and deliver said files using
TransmitFile.

So, for the record (and anyone looking at this in the future), completely disregard this. Bad idea.

Err, that was kind of rude Bertrand... Well no worries, none of our modules have this in them @ the gallery.

We put such files in App_Data as there is already a folder for site specific data that shouldn't be available publicly, like the settings.

Also, your remark only holds if you would allow semi-direct access to the data contained there (for example you have a download controller that accepts a file name and you would just allow to download it without any decent checks)

To dl files from it, we work with ids that are mappable to those specific files: we simply do not offer a method to download files by file name.

Maybe a bad idea by default, but I'd defend our usage as being secure... Not every coder is the same you know, some people do spend the extra time to secure things...

Anyway, for people reading this, whenever you see a remark from us posted here, keep in mind that we do not have a simple orchard website: it is doing plenty of stuff in the background, hosts multiple WCF services, contains our API that is being used by our
software and will soon contain a custom wcf server that acts as an end point for our custom 'always on' system that we'll be implementing soon in our software.

So yes, I'll talk to a colleague about your remark Bertrand, maybe we'll switch to another solution now, just to 'comply' with general security rules, I just wished you put it more friendly instead of the threatening us with being perm banned from the gallery...
Even if it is a joke, it sure is not a funny one...

The framework takes special precautions protecting app_data. By providing access to even parts of it, you're going around those precautions and opening a backdoor. Don't do that. Instead, create your own protected folder for that usage. Hell is paved with
good intentions.

And learn to take a joke, especially when it's clearly labeled as such.

I have a module I built for local use I call "Rework.Filestream" which provides a framework to stream a "secure file". All you need to do is set up a web.config in your "secure folder" preventing visitors from directly browsing
to the folder by url. After that, it uses role based security to control who can access the files in the folder. Everything works good (I think so at least) except currently the role security is not unique by content type but is global (i.e. you can only have
one secure document folder per website and access control is not based by content type). To make it better, it really should have the secure folder setting handled on the content type and have the role based security exposed at the content type level (instead
of globally).

I also have a module I call "Rework.MediaPlayer" which allows you to do a similar thing with secure video file streaming (html5 and flash fallback). The module is functional though it has a similar limitation to the one above, one security setting/folder
globally.

The one thing I am displeased about (though I see no way around it) is that there is no way I know of to have the module itself deploy a web.config so it will still involve a user putting in place their own web.config file for each folder they want to secure.

With all that said, if you think these will be helpful (and I would also love some help in improving the modules) I can release them under open source on codeplex. Let me know if they sounds useful.

The media module is designed for publicly accessible files. If you need protected resoruces, you should store the binaries into the database and use regular content item permissions

How would you go about storing "the binaries into the database"? I have a scenario where I'd like to use content permissions so using regular content would be fine. I just need to be able to attach files to content and store them in the database.

Thank you for the reply. I've been looking over the MediaLibrary module.

I also have done the content part/fields tutorials. I've scoured for documentation. I'm having difficulty bringing everything full circle.

So, I created the following pretty much dead-on what the tutorials did: SecureMediaRecord, Part, Driver, Handler and the migration. I want to upload a file and save the file stream to the database in a binary field along w/ the filename, a title and description
field. Where do you do the save part? In a controller?

I'm trying to start this whole thing very simple so I can understand how everything works together.

I have my secure media part wrapped in a secure media content type that's marked as "createable". This puts the link in the admin area for Create New under Page and Projection.

I created an INavigationProvider for the admin area which puts a new link in the admin area as well. But what I'd really like to do is use the create new content link that gets generated by the "createable" flag for the content type.

My question would be then, how do you inject your own controller or process that allows you to do the saving of the new content type yourself? Because mine bombs in the IRepository, and never hits anything I create. When debugging, I notice that inside the
ContentItem, the SecureMediaPart stuff is all null. So I'm guessing I need to inject something, somewhere that will bind up the properties of the SecureMediaPart object to the values entered in the view.

Basically, I have the Views/Editor/Parts/SecureMedia.cshtml view and I have all the stuff there I need. But when you save or publish, it goes...... where?

I tried to install on a fresh Orchard install and I got an error saying:

The assembly reference 'Microsoft.WindowsAzure.ServiceRuntime, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL' could not be loaded for module 'CloudConstruct.SecureFileField'.

Hmm it is looking for the Azure SDK 1.8 assembly. You will need to install that. I am unsure why the published version did not come with it. If the dll is in the module bin directory then just re-add the reference.

Since I marked it as "createable", it added a link under the "new" admin menu. When I use that menu to create a new SecureMediaContentType, it "works", but just doesn't save the file data. It saves the other fields in my SecureMediaPart,
just not the file data.

So, what I did was instead of going that route and trying to figure out what was doing the saving, I created an INavigationProvider that routed to my own SecureMediaAdminController. Then I wrote my own code to save the content part. This worked. I was able
to get the file data to save. But now I have to figure out how to save the content item permissions with that....

OR, I need to figure out why the first attempt didn't save the file data.

You would need to lookup the content item's permissions and apply them in your controller by hand. So only serve the file if the user has the permissions that are in the associated permissions part of your content item.

I think your best bet is to just install the Azure 1.8 SDK and my module will do everything you need.

And I'm completely cool with that. Except I think it's missing a route for the SecureFileFieldController. I get a 404 when trying to view the actual file. The Display view works ok. There's a link to the file. But the link points to the SecureFileFieldController
and gets a 404.

Yea, I followed the instructions. Saves ok. I see the saved PDF in my directory I created. But still getting a 404 when trying to view the content. The IIS error page lists the physical location for my site incorrectly though, which is weird.

The controller is required to serve the file from the directory. It is strange to me that the Controller is not being hit at all and there are no errors in the logs. If you have skype I am happy to help. arra.derderian

No, that's all good. It's that my web app is actually a virtual directory under another site. So the secure media link is pointing to the root of the web instead of my orchard site. I changed the view to pre-pend "/orchardlocal" to the link and
it works.

I was just curious as to if I could customize the SecureUrl and SharedAccessUrl so that I didn't have to do that in the view.

What if I wanted to stream the file to the browser in the Display() action in the driver? The way the controller does. Is there a DriverResult that'll jive w/ that? Or is it too late in the pipeline to do something like that?

I don't think you can do that. (This is just me saying that.) The Driver returns a DriverResult (a shape) which is really to build up your model to be returned to the View. You would be disrupting the Orchard pipeline by returning something different.
Perhaps you could modify the core so Display() could return a FileResult as well, but you would need to investigate.

The other way is in the View file, you could possibly modify the response there and stream the file from that location. So instead of showing the link with markup, you would change the response type and add the file to the response stream.

Let's assume for now that I'm only dealing with PDF type documents, and yes I'd like to stream it directly to the browser as in content-type: application/pdf and writing it directly to the response stream. Basically what the controller does when you click
the link from the Display view.

For other document types (pictures, videos), I can just check the extension and display the appropriate HTML element.

What I did as a work-around for now is, I modified the SecureFile.cshtml view to check the file extension and if it's a PDF, I just do a location.href to the URL in the model. There's a slight moment when you actually see the Display view before it switches
to the PDF, but it's acceptable for now.