Jump to content

Protected files


raydale

Recommended Posts

My next coming project might need secure files download probably.

Take a look at the AmazonS3CloudFront module - I was using Hani's .htaccess method but am now using S3/CloudFront + signed URLs to deliver content securely.

Works great and also takes the load of supplying the file off the web server.

  • Like 3
Link to comment
Share on other sites

  • 5 months later...

I have a solution that 90% works, which is good enough for my needs. I have the complicated situation where there's an Images field, which needs to remain public, and a Documents field which needs to be protected in some situations.

In my setup, I have a checkbox field is_member_page which is used to require authentication to view the page, so I've also used that in my hooks:

# For files that should be protected, set the path to the .page-id files directory. Should auto-create the directory.
$this->addHookAfter('PagefilesManager::path', function($event) {

	$page = $event->object->page;

	if ( $page->is_member_page )
	{
		$event->return = dirname($event->return) . '/.' . $page->id . '/';
	}

});

I was using this next hook for a while, so the URL in the admin area pointed automatically to the download pass-through page. What I ran into is that this broke the preview in the Images field, though, so I opted against it. This means when I insert links to the files, I have to manually craft them. It's a minor inconvenience for being able to still preview and easily insert the images, though.

# Optional; if you want to change the URL to automatically point to the download pass-through page.
# Be sure to replace/set $YOUR_PAGE_ID as appropriate.
$this->addHookAfter('PagefilesManager::url', function($event) {
	$page = $event->object->page;

	if ( $page->is_member_page )
	{
		$event->return = wire('pages')->get($YOUR_PAGE_ID)->url . $event->object->page . '/' . $event->object->name;
	}

});

This is where it gets a bit tricky, and violates DRY a bit. 

The above hooks affect the path and url for all file fields. I tried and tried, but I could not conditionally change them for only the Documents field while leaving the Images field. Instead, what I've done is checked if the inputfield is a FIle or an Image. If it's an image, I make a copy of it in the public folder, so it can be previewed in the backend (and used on the page :)).

$this->addHook('InputfieldFile::fileAdded', function($event) {

	if ( get_class($event->object) == 'InputfieldFile' )
	{
		$file = $event->arguments(0);
		$path = $file->filename();
		$page = $file->page;

		# if: member only page
		if ( $page->is_member_page )
		{
			$secure_path = dirname(dirname($path)) . '/.' . $page->id . '/';

			if ( !$file->copyToPath($secure_path) )
			{
				$this->log->save('messages', "Error copying secure file to: {$secure_path}");
			}

		} # end if

	}
	else if ( get_class($event->object) == 'InputfieldImage' )
	{
		$file = $event->arguments(0);
		$path = $file->filename();
		$page = $file->page;

		$public_dir = dirname(dirname($path)) . '/' . $page->id . '/';

		# if: file does not exist in public dir yet; copy it
		if ( !file_exists($public_dir . $file->name) )
		{

			if ( !$file->copyToPath($public_dir) )
			{
				$this->log->save('messages', "Error copying file to: {$public_dir}");
			}

		} # end if

	}

});

Finally, to clean up my DRY mess, I make sure when an image is deleted from the protected directory, it's also deleted from the public directory.

$this->addHook('InputfieldFile::processInputDeleteFile', function($event) {
	$file = $event->arguments(0);
	$path = $file->filename();
	$page = $file->page;

	$public_path = dirname(dirname($path)) . '/' . $page->id . '/' . $file->name;

	if ( file_exists($public_path) )
	{
		unlink($public_path);
	}

});

My download pass-through page template is similar to the ones mentioned earlier in this thread, though I check the file extension and if it's a PDF I deliver it with MIME type application/pdf and not with Content-Disposition: attachment -- that way the PDF can be previewed in modern browsers, depending on the user's settings. (I can share this template too, if anyone is interested).

Any feedback is welcome!

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...