mirror of
https://github.com/silverstripe/silverstripe-userforms.git
synced 2024-10-22 17:05:42 +02:00
API Integrate with secure assets module
This commit is contained in:
parent
c4a95d59dc
commit
1dd787327a
@ -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
16
_config/secureassets.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
Name: userformssecurity
|
||||||
|
Only:
|
||||||
|
ModuleExists: secureassets
|
||||||
|
---
|
||||||
|
EditableFileField:
|
||||||
|
extensions:
|
||||||
|
- SecureEditableFileField
|
||||||
|
|
||||||
|
---
|
||||||
|
Name: userformsnosecurity
|
||||||
|
Except:
|
||||||
|
ModuleExists: secureassets
|
||||||
|
---
|
||||||
|
EditableFileField:
|
||||||
|
hidden: true
|
144
code/extensions/SecureEditableFileField.php
Normal file
144
code/extensions/SecureEditableFileField.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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": {
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -54,4 +82,4 @@ UserDefinedForm:
|
|||||||
email_template_directory: your/template/path/
|
email_template_directory: your/template/path/
|
||||||
````
|
````
|
||||||
|
|
||||||
Any SilverStripe templates placed in your `email_template_directory` directory will be available for use with submission emails.
|
Any SilverStripe templates placed in your `email_template_directory` directory will be available for use with submission emails.
|
83
tests/SecureEditableFileFieldTest.php
Normal file
83
tests/SecureEditableFileFieldTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user