Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

We've moved the forum!

Please use forum.silverstripe.org for any new questions (announcement).
The forum archive will stick around, but will be read only.

You can also use our Slack channel or StackOverflow to ask for help.
Check out our community overview for more options to contribute.

All other Modules /

Discuss all other Modules here.

Moderators: martimiz, Sean, Ed, biapar, Willr, Ingo, swaiba

Secure Files: securing personal files


Go to End


22 Posts   5941 Views

Avatar
Hamish

Community Member, 712 Posts

5 October 2010 at 9:36am

Edited: 05/10/2010 9:38am

Hi,

It might be a bit risky trying to replicate the behaviour of CanViewSecured - better just to call it directly, but the solution will depend on a few things, like how many files there are and how much traffic you're likely to get.

The easiest way would be to simply return an object with all Files to the template and call CanView (CanViewSecured is really an internal API method), eg (these examples are untested):

// MODEL:
<?php
function getAllFiles() {
	return DataObject::get('File');
}
?>

// TEMPLATE:
<% if AllFiles %>
	<% control AllFiles %>
		<% if CanView %>
			<p>Download <a href="$URL">$Name</a></p>
		<% end_if %>
	<% end_control %>
<% end_if %>

However this is going to be database intensive, especially if you have lots of files or folders.

Another solution would be to test Folders instead of Files, then return the Files in Folders that the current Member can view:

// MODEL:
function getViewableFiles() {
	$files = new DataObjectSet();
	$folders = DataObject::get('Folder');
	if(!$folders) return $files;
	foreach($folders as $folder)
		if($folder->CanView())
			if($subFiles = DataObject::get('File', "ClassName != 'Folder' && ParentID = {$folder->ID}"))
				$files->merge($subFiles);
	return $files; // Should contain all files that the member can view.
}

This will be more efficient that the first option, but it's still pretty hard on the database if there are a lot of folders.

The best option would probably be an extension that cached view permissions somehow, but that would be an advanced option if the above options were not suitable.

Hope this helps.

Hamish

Avatar
klikhier

Community Member, 150 Posts

7 October 2010 at 11:01pm

Hamish, second solution didn't work (haven't tried first one). Something goes wrong with if($folder->canView()), because all available files in the Uploads folder are added tot $files. Have tried canView, CanView and canViewSecured.

Avatar
Hamish

Community Member, 712 Posts

8 October 2010 at 11:48am

Ah, well you can use the property Secured to check if the current folder is marked secure and the method "InheritSecured()" to test if the and parent is secured, so modify:

...
foreach($folders as $folder) 
	if(!$folder->Secured && !$folder->InheritSecured()) 
		continue;
	if($folder->CanView()) 
		....

Avatar
Cano

Community Member, 14 Posts

27 October 2010 at 4:59am

Jumping into this thread as it seems right up my alley. (Thanks for the link Hamish)

I have uncommented the line below:

// Assign file security by individual member:
DataObject::add_extension('File', 'SecureFileMemberPermissionDecorator');

I have then gone into the backend and assigned the appropriate user permissions to the files. So far so good.

Then in my template I inserted this (InvestorPage.ss):

<% if AllFiles %>
   <% control AllFiles %>
      <% if CanView %>
         <p>Download <a href="$URL">$Name</a></p>
      <% end_if %>
   <% end_control %>
<% end_if %>

And into my php file i now have:

<?php
/**
 * Defines the InvestorPage page type
 */
 	class InvestorPage extends Page {
		static $db = array(
		);
		static $has_one = array(
			'BannerImage' => 'Image',
		);
		static $has_many = array (
			'File' => 'File',
		);
		function getAllFiles() {
   		return DataObject::get('File');
		} 
		function getCMSFields() {
			$fields = parent::getCMSFields();
			$fields->removeFieldFromTab('Root.Content.Main','PagePhoto');
			$fields->removeFieldFromTab('Root.Content.Main','Content');
		return $fields;
		}
	}
	class InvestorPage_Controller extends Page_Controller { 
	}

It does sucessfully filter out other users files, but it shows every other file uploaded included site imagery etc. How can i remove all the excess and just show the contents of files within certain (secured) folders?

Sorry this is a Noob question, but im really learning quite rapidly here (bear with me!!!)

Thanks
Cano

Avatar
Cano

Community Member, 14 Posts

27 October 2010 at 10:13pm

Edited: 27/10/2010 10:15pm

I have a feeling it has something to do with the CanView element...

<div class="typography">
		<% if AllFiles %>
   <% control AllFiles %>
      <% if CanView %>
        <p>Download <a href="$URL">$Name</a></p>
      <% end_if %>
   <% end_control %>
<% end_if %>
</div>

Should it be...

<% if canViewSecured %>

rather than...???

<% if CanView %>

Cheers,
Hope this extra info shows that I'm at least trying!

Avatar
Hamish

Community Member, 712 Posts

29 October 2010 at 12:41pm

canView just calls canViewSecured (they operate slightly differently when extended, hence the seperation). Sounds like a solution be to additionally filter by files that are in secure folders, e.g. building on what you already have:

<% if AllFiles %> 
	<% control AllFiles %> 
		<% if InheritSecured %><% if CanView %> 
			<p>Download <a href="$URL">$Name</a></p> 
		<% end_if %><% end_if %> 
	<% end_control %> 
<% end_if %> 

Basically it just checks the folder stack to see if it is in a tree that has been marked secure. That should filter out any other site assets that are sitting around in other folders.

Avatar
Cano

Community Member, 14 Posts

29 October 2010 at 11:56pm

Edited: 30/10/2010 12:15am

Perfect, you genuinly made me jump for joy with that one!

One last thing...if the files that the user has access to are in a variety of folders, is there a way to orgainse the files by these different folders? i.e. the attatched/linked screenshot? http://screencast.com/t/ouG5IX0LN

Here is my Investor Page...

class InvestorPage extends Page {
		static $db = array(
		);
		static $has_one = array(
			'BannerImage' => 'Image',
		);
		static $has_many = array (
			'File' => 'File',
		);
		function getAllFiles() {
   			return DataObject::get('File');
		}
		function getCMSFields() {
			$fields = parent::getCMSFields();
			$fields->removeFieldFromTab('Root.Content.Main','PagePhoto');
			$fields->removeFieldFromTab('Root.Content.Main','Content');
		return $fields;
		}
	}
	class InvestorPage_Controller extends Page_Controller { 
	}

And the template (thanks to you!)...

<% if AllFiles %>
        <% control AllFiles %>
            <% if InheritSecured %><% if CanView %>
                	<p><a href="$URL">$Name</a></p>
                <% end_if %><% end_if %>
        <% end_control %>
    <% end_if %>

My file heirarchy is also attached.

Is there a way to say if in x folder - show files?

Ideally, the client may wish to add other folders so it would be good if the solution didnt rely on absolute paths (which is the method ive been trying)

Avatar
Hamish

Community Member, 712 Posts

30 October 2010 at 1:48pm

Edited: 30/10/2010 1:49pm

You should probably build the data before pushing it to your template if you need sorting / nesting etc. Ie, a method on the page that does similar checks to those described above, but returns files in DataObjectSets that have the name of the folder. Something like:

function getFilesUserCanAccess() {
	$result = new DataObjectSet();
	$folders = array();
	$files = DataObject::get('Files');
	foreach($files as $file) {
		if(empty($folders[$file->ParentID]))
			$folders[$file->ParentID] = array(
				'Folder' => $file->Parent(),  // I think Parent() gets the file folder?
				'Files' => array()
			);
		if($file->canView() && $file->InheritSecured())
			$folders[$file->ParentID]['Files'][] = $file;
	}
	foreach($folders as $folder) {
		$files = new DataObjectSet();
		foreach($folder['Files'] as $file)
			$files->push($file);
		$result->push(new ArrayData(array('Folder' => $folder['Folder'], $files => $files));
	}
	return $result;
}

Should be able to use it a template like:

<% control FilesUSerCanAccess %>
	<p>$Folder.Name</p>
	<% control Files %>
		<p>$URL</p> // etc
	<% end_if %>
<% end_control %>

None of the above is tested.