API Integrate with secure assets module

This commit is contained in:
Damian Mooyman 2015-08-28 13:21:57 +12:00
parent c4a95d59dc
commit 1dd787327a
6 changed files with 283 additions and 7 deletions

View File

@ -29,10 +29,14 @@ matrix:
env: DB=PGSQL CORE_RELEASE=3.1 env: DB=PGSQL CORE_RELEASE=3.1
- php: 5.6 - php: 5.6
env: DB=PGSQL CORE_RELEASE=3.1 env: DB=PGSQL CORE_RELEASE=3.1
include:
- php: 5.4
env: DB=MYSQL CORE_RELEASE=3.1 SECUREASSETS=1
before_script: before_script:
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support - git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss - "if [ \"$SECUREASSETS\" = \"\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss; fi"
- "if [ \"$SECUREASSETS\" = \"1\" ]; then php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/secureassets; fi"
- cd ~/builds/ss - cd ~/builds/ss
script: script:

16
_config/secureassets.yml Normal file
View File

@ -0,0 +1,16 @@
---
Name: userformssecurity
Only:
ModuleExists: secureassets
---
EditableFileField:
extensions:
- SecureEditableFileField
---
Name: userformsnosecurity
Except:
ModuleExists: secureassets
---
EditableFileField:
hidden: true

View File

@ -0,0 +1,144 @@
<?php
/**
* Provides additional file security for uploaded files when the securefiles module is installed
*
* {@see EditableFileField}
*/
class SecureEditableFileField extends DataExtension {
/**
* Path to secure files location under assets
*
* @config
* @var type
*/
private static $secure_folder_name = 'SecureUploads';
/**
* Disable file security if a user-defined mechanism is in place
*
* @config
* @var bool
*/
private static $disable_security = false;
/*
* Check if file security is enabled
*
* @return bool
*/
public function getIsSecurityEnabled() {
// Skip if requested
if($this->owner->config()->disable_security) {
return false;
}
// Check for necessary security module
if(!class_exists('SecureFileExtension')) {
trigger_error('SecureEditableFileField requires secureassets module', E_USER_WARNING);
return false;
}
return true;
}
public function requireDefaultRecords() {
// Skip if disabled
if(!$this->getIsSecurityEnabled()) {
return;
}
// Update all instances of editablefilefield which do NOT have a secure folder assigned
foreach(EditableFileField::get() as $fileField) {
// Skip if secured
if($fileField->getIsSecure()) {
continue;
}
// Force this field to secure itself on write
$fileField->write(false, false, true);
DB::alteration_message(
"Restricting editable file field \"{$fileField->Title}\" to secure folder",
"changed"
);
}
}
/**
* Secure this field before saving
*/
public function onBeforeWrite() {
$this->makeSecure();
}
/**
* Ensure this field is secured, but does not write changes to the database
*/
public function makeSecure() {
// Skip if disabled or already secure
if(!$this->getIsSecurityEnabled() || $this->owner->getIsSecure()) {
return;
}
// Ensure folder exists
$folder = $this->owner->Folder();
if(!$folder || !$folder->exists()) {
// Create new folder in default location
$folder = Folder::find_or_make($this->owner->config()->secure_folder_name);
$this->owner->FolderID = $folder->ID;
} elseif($this->isFolderSecured($folder)) {
// If folder exists and is secure stop
return;
}
// Make secure
$folder->CanViewType = 'OnlyTheseUsers';
$folder->ViewerGroups()->add($this->findAdminGroup());
$folder->write();
}
/**
* Find target group to record
*
* @return Group
*/
protected function findAdminGroup() {
singleton('Group')->requireDefaultRecords();
return Permission::get_groups_by_permission('ADMIN')->First();
}
/**
* Determine if the field is secure
*
* @return bool
*/
public function getIsSecure() {
return $this->isFolderSecured($this->owner->Folder());
}
/**
* Check if a Folder object is secure
*
* @param Folder $folder
* @return boolean
*/
protected function isFolderSecured($folder) {
if(! ($folder instanceof Folder) || !$folder->exists()) {
return false;
}
switch($folder->CanViewType) {
case 'OnlyTheseUsers':
return true;
case 'Inherit':
$parent = $folder->Parent();
return $parent && $parent->exists() && $this->isFolderSecured($parent);
case 'Anyone':
case 'LoggedInUsers':
default:
return false;
}
}
}

View File

@ -15,7 +15,8 @@
"silverstripe/segment-field": "^1.0" "silverstripe/segment-field": "^1.0"
}, },
"suggest": { "suggest": {
"colymba/gridfield-bulk-editing-tools": "Allows for bulk management of form submissions" "colymba/gridfield-bulk-editing-tools": "Allows for bulk management of form submissions",
"silverstripe/secureassets": "Enables files uploaded via userforms to be secured from public access"
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {

View File

@ -21,8 +21,8 @@ You should see a new PageType in the CMS 'User Defined Form'. This has a new 'Fo
## File Uploads and Security ## File Uploads and Security
The module allows adding a "File Upload Field" to a form, The module optionally allows adding a "File Upload Field" to a form.
which enables users of this form to upload files to the website's assets The field enables users of this form to upload files to the website's assets
so they can be viewed later by CMS authors. Small files so they can be viewed later by CMS authors. Small files
are also attached to the (optional) email notifications are also attached to the (optional) email notifications
to any configured recipients. to any configured recipients.
@ -35,15 +35,43 @@ configuration setting.
The allowed upload size is determined by PHP configuration The allowed upload size is determined by PHP configuration
for this website (the smaller value of `upload_max_filesize` or `post_max_size`). for this website (the smaller value of `upload_max_filesize` or `post_max_size`).
The field is disabled by default since implementors need to determine how files are secured.
Since uploaded files are kept in `assets/` folder of the webroot, there is no built-in Since uploaded files are kept in `assets/` folder of the webroot, there is no built-in
permission control around who can view them. It is unlikely permission control around who can view them. It is unlikely
that website users guess the URLs to uploaded files unless that website users guess the URLs to uploaded files unless
they are specifically exposed through custom code. they are specifically exposed through custom code.
Nevertheless, you should think carefully about the use case for file uploads. You should think carefully about the use case for file uploads.
Unauthorised viewing of files might be desired, e.g. submissions for public competitions. Unauthorised viewing of files might be desired, e.g. submissions for public competitions.
In other cases, submissions could be expected to contain private data. In other cases, submissions could be expected to contain private data.
Please consider securing these files, e.g. through the [secureassets](http://addons.silverstripe.org/add-ons/silverstripe/secureassets) module.
In order to enable this field it is advisable to install the
[secureassets](http://addons.silverstripe.org/add-ons/silverstripe/secureassets) module.
This can be done using the below composer command:
```
composer require silverstripe/secureassets ~1.0.4
```
This step will have the following effects:
* The "File Upload Field" will be enabled through the CMS interface
* By default any new file upload fields will be assigned to a default folder, `SecureUploads`,
which can only be accessed by admins.
* Any existing file upload fields, if they are not assigned to a folder, will now upload
to this folder.
* Any existing file upload fields which are assigned folders will have the security settings
for those folders updated so that only admins can access them.
This functionality can be configured in the following ways:
* Assigning another group to the `SecureUploads` folder will allow users from that group
(rather than admins only) to access those files.
* The folder name can be configured using the `EditableFileField.secure_folder_name` config option.
* Security functionality can be disabled (although this is not advisable) by setting
`EditableFileField.disable_security` to true.
### Custom email templates ### Custom email templates

View File

@ -0,0 +1,83 @@
<?php
/**
* Tests integration of EditableFileField with the securefiles module
*
* @author dmooyman
*/
class SecureEditableFileFieldTest extends SapphireTest {
protected $usesDatabase = true;
public function setUp() {
parent::setUp();
if(!class_exists('SecureFileExtension')) {
$this->skipTest = true;
$this->markTestSkipped(get_class() . ' skipped unless running with securefiles');
}
Config::inst()->update('EditableFileField', 'secure_folder_name', 'SecureEditableFileFieldTest/SecureUploads');
$this->clearPath();
}
public function tearDown() {
$this->clearPath();
parent::tearDown();
}
protected function clearPath() {
if(file_exists(ASSETS_PATH . '/SecureEditableFileFieldTest')) {
Filesystem::removeFolder(ASSETS_PATH . '/SecureEditableFileFieldTest');
}
}
/**
* Test that newly created folders are secure
*/
public function testCreateFolder() {
$field = new EditableFileField();
$field->write();
$this->assertTrue($field->getIsSecure());
$this->assertTrue($field->Folder()->exists());
$this->assertEquals('assets/SecureEditableFileFieldTest/SecureUploads/', $field->Folder()->Filename);
$this->assertEquals('OnlyTheseUsers', $field->Folder()->CanViewType);
$this->assertEquals(1, $field->Folder()->ViewerGroups()->first()->Permissions()->filter('code', 'ADMIN')->count());
}
/**
* Test new folders that are created without security enabled
*/
public function testCreateInsecure() {
Config::inst()->update('EditableFileField', 'disable_security', true);
// Esure folder is created without a folder
$field = new EditableFileField();
$field->write();
$this->assertFalse($field->getIsSecure());
$this->assertFalse($field->Folder()->exists());
// Assigning a non-secure folder doesn't secure this
$folder = Folder::find_or_make('SecureEditableFileFieldTest/PublicFolder');
$field->FolderID = $folder->ID;
$field->write();
$this->assertFalse($field->getIsSecure());
$this->assertTrue($field->Folder()->exists());
$this->assertEquals('assets/SecureEditableFileFieldTest/PublicFolder/', $field->Folder()->Filename);
$this->assertEquals('Inherit', $field->Folder()->CanViewType);
// Enabling security and re-saving will force this field to be made secure (but not changed)
Config::inst()->update('EditableFileField', 'disable_security', false);
singleton('EditableFileField')->requireDefaultRecords();
// Reload record from DB
$field = EditableFileField::get()->byID($field->ID);
// Existing folder is now secured (retro-actively secures any old uploads)
$this->assertTrue($field->getIsSecure());
$this->assertTrue($field->Folder()->exists());
$this->assertEquals('assets/SecureEditableFileFieldTest/PublicFolder/', $field->Folder()->Filename);
$this->assertEquals('OnlyTheseUsers', $field->Folder()->CanViewType);
$this->assertEquals(1, $field->Folder()->ViewerGroups()->first()->Permissions()->filter('code', 'ADMIN')->count());
}
}