mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Check model permissions in GridField
This commit is contained in:
parent
22eeaa4ac1
commit
1848d7e90a
@ -44,6 +44,39 @@ you'll need to adjust your code.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### GridField and ModelAdmin Permission Checks
|
||||||
|
|
||||||
|
`GridFieldDetailForm` now checks for `canEdit()` and `canDelete()` permissions
|
||||||
|
on your model. `GridFieldAddNewButton` checks `canCreate()`.
|
||||||
|
The default implementation requires `ADMIN` permissions.
|
||||||
|
You'll need to loosen those permissions if you want other users with CMS
|
||||||
|
access to interact with your data.
|
||||||
|
Since `GridField` is used in `ModelAdmin`, this change will affect both classes.
|
||||||
|
|
||||||
|
Example: Require "CMS: Pages section" access
|
||||||
|
|
||||||
|
:::php
|
||||||
|
class MyModel extends DataObject {
|
||||||
|
public function canView($member = null) {
|
||||||
|
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
|
||||||
|
}
|
||||||
|
public function canEdit($member = null) {
|
||||||
|
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
|
||||||
|
}
|
||||||
|
public function canDelete($member = null) {
|
||||||
|
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
|
||||||
|
}
|
||||||
|
public function canCreate($member = null) {
|
||||||
|
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also implement [custom permission codes](/topics/permissions).
|
||||||
|
For 3.1.0 stable, we aim to further simplify the permission definitions,
|
||||||
|
in order to reduce the boilerplate code required to get a model editable in the CMS.
|
||||||
|
|
||||||
|
Note: GridField is already relying on the permission checks performed
|
||||||
|
through the CMS controllers, providing a simple level of security.
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
* `TableListField`, `ComplexTableField`, `TableField`, `HasOneComplexTableField`, `HasManyComplexTableField` and `ManyManyComplexTableField` have been removed from the core and placed into a module called "legacytablefields" located at https://github.com/silverstripe-labs/legacytablefields
|
* `TableListField`, `ComplexTableField`, `TableField`, `HasOneComplexTableField`, `HasManyComplexTableField` and `ManyManyComplexTableField` have been removed from the core and placed into a module called "legacytablefields" located at https://github.com/silverstripe-labs/legacytablefields
|
||||||
|
@ -310,6 +310,16 @@ transfered between page requests by being inserted as a hidden field in the form
|
|||||||
|
|
||||||
A GridFieldComponent sets and gets data from the GridState.
|
A GridFieldComponent sets and gets data from the GridState.
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
Since GridField is mostly used in the CMS, the controller managing a GridField instance
|
||||||
|
will already do some permission checks for you, and can decline display or executing
|
||||||
|
any logic on your field.
|
||||||
|
|
||||||
|
If you need more granular control, e.g. to consistently deny non-admins from deleting
|
||||||
|
records, use the `DataObject->can...()` methods
|
||||||
|
(see [DataObject permissions](/reference/dataobject#permissions)).
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
* [ModelAdmin: A UI driven by GridField](/reference/modeladmin)
|
* [ModelAdmin: A UI driven by GridField](/reference/modeladmin)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* This component provides a button for opening the add new form provided by {@link GridFieldDetailForm}.
|
* This component provides a button for opening the add new form provided by {@link GridFieldDetailForm}.
|
||||||
|
* Only returns a button if {@link DataObject->canCreate()} for this record returns true.
|
||||||
*
|
*
|
||||||
* @package framework
|
* @package framework
|
||||||
* @subpackage gridfield
|
* @subpackage gridfield
|
||||||
@ -21,9 +22,12 @@ class GridFieldAddNewButton implements GridField_HTMLProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getHTMLFragments($gridField) {
|
public function getHTMLFragments($gridField) {
|
||||||
|
$singleton = singleton($gridField->getModelClass());
|
||||||
|
if(!$singleton->canCreate()) return array();
|
||||||
|
|
||||||
if(!$this->buttonName) {
|
if(!$this->buttonName) {
|
||||||
// provide a default button name, can be changed by calling {@link setButtonName()} on this component
|
// provide a default button name, can be changed by calling {@link setButtonName()} on this component
|
||||||
$objectName = singleton($gridField->getModelClass())->i18n_singular_name();
|
$objectName = $singleton->i18n_singular_name();
|
||||||
$this->buttonName = _t('GridField.Add', 'Add {name}', array('name' => $objectName));
|
$this->buttonName = _t('GridField.Add', 'Add {name}', array('name' => $objectName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,15 +98,16 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
|
|||||||
*/
|
*/
|
||||||
public function getColumnContent($gridField, $record, $columnName) {
|
public function getColumnContent($gridField, $record, $columnName) {
|
||||||
if($this->removeRelation) {
|
if($this->removeRelation) {
|
||||||
|
if(!$record->canEdit()) return;
|
||||||
|
|
||||||
$field = GridField_FormAction::create($gridField, 'UnlinkRelation'.$record->ID, false,
|
$field = GridField_FormAction::create($gridField, 'UnlinkRelation'.$record->ID, false,
|
||||||
"unlinkrelation", array('RecordID' => $record->ID))
|
"unlinkrelation", array('RecordID' => $record->ID))
|
||||||
->addExtraClass('gridfield-button-unlink')
|
->addExtraClass('gridfield-button-unlink')
|
||||||
->setAttribute('title', _t('GridAction.UnlinkRelation', "Unlink"))
|
->setAttribute('title', _t('GridAction.UnlinkRelation', "Unlink"))
|
||||||
->setAttribute('data-icon', 'chain--minus');
|
->setAttribute('data-icon', 'chain--minus');
|
||||||
} else {
|
} else {
|
||||||
if(!$record->canDelete()) {
|
if(!$record->canDelete()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
$field = GridField_FormAction::create($gridField, 'DeleteRecord'.$record->ID, false, "deleterecord",
|
$field = GridField_FormAction::create($gridField, 'DeleteRecord'.$record->ID, false, "deleterecord",
|
||||||
array('RecordID' => $record->ID))
|
array('RecordID' => $record->ID))
|
||||||
->addExtraClass('gridfield-button-delete')
|
->addExtraClass('gridfield-button-delete')
|
||||||
@ -132,13 +133,20 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
|
|||||||
if(!$item) {
|
if(!$item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if($actionName == 'deleterecord' && !$item->canDelete()) {
|
|
||||||
throw new ValidationException(
|
|
||||||
_t('GridFieldAction_Delete.DeletePermissionsFailure',"No delete permissions"),0);
|
|
||||||
}
|
|
||||||
if($actionName == 'deleterecord') {
|
if($actionName == 'deleterecord') {
|
||||||
|
if(!$item->canDelete()) {
|
||||||
|
throw new ValidationException(
|
||||||
|
_t('GridFieldAction_Delete.DeletePermissionsFailure',"No delete permissions"),0);
|
||||||
|
}
|
||||||
|
|
||||||
$item->delete();
|
$item->delete();
|
||||||
} else {
|
} else {
|
||||||
|
if(!$item->canEdit()) {
|
||||||
|
throw new ValidationException(
|
||||||
|
_t('GridFieldAction_Delete.EditPermissionsFailure',"No permission to unlink record"),0);
|
||||||
|
}
|
||||||
|
|
||||||
$gridField->getList()->remove($item);
|
$gridField->getList()->remove($item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,16 +310,31 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
|||||||
return $controller->redirect($noActionURL, 302);
|
return $controller->redirect($noActionURL, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$canView = $this->record->canView();
|
||||||
|
$canEdit = $this->record->canEdit();
|
||||||
|
$canDelete = $this->record->canDelete();
|
||||||
|
$canCreate = $this->record->canCreate();
|
||||||
|
|
||||||
|
if(!$canView) {
|
||||||
|
$controller = Controller::curr();
|
||||||
|
// TODO More friendly error
|
||||||
|
return $controller->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
$actions = new FieldList();
|
$actions = new FieldList();
|
||||||
if($this->record->ID !== 0) {
|
if($this->record->ID !== 0) {
|
||||||
$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Save', 'Save'))
|
if($canEdit) {
|
||||||
->setUseButtonTag(true)
|
$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Save', 'Save'))
|
||||||
->addExtraClass('ss-ui-action-constructive')
|
->setUseButtonTag(true)
|
||||||
->setAttribute('data-icon', 'accept'));
|
->addExtraClass('ss-ui-action-constructive')
|
||||||
|
->setAttribute('data-icon', 'accept'));
|
||||||
|
}
|
||||||
|
|
||||||
$actions->push(FormAction::create('doDelete', _t('GridFieldDetailForm.Delete', 'Delete'))
|
if($canDelete) {
|
||||||
->setUseButtonTag(true)
|
$actions->push(FormAction::create('doDelete', _t('GridFieldDetailForm.Delete', 'Delete'))
|
||||||
->addExtraClass('ss-ui-action-destructive'));
|
->setUseButtonTag(true)
|
||||||
|
->addExtraClass('ss-ui-action-destructive'));
|
||||||
|
}
|
||||||
|
|
||||||
}else{ // adding new record
|
}else{ // adding new record
|
||||||
//Change the Save label to 'Create'
|
//Change the Save label to 'Create'
|
||||||
@ -353,6 +368,14 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
|||||||
|
|
||||||
$form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT);
|
$form->loadDataFrom($this->record, $this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT);
|
||||||
|
|
||||||
|
if($this->record->ID && !$canEdit) {
|
||||||
|
// Restrict editing of existing records
|
||||||
|
$form->makeReadonly();
|
||||||
|
} elseif(!$this->record->ID && !$canCreate) {
|
||||||
|
// Restrict creation of new records
|
||||||
|
$form->makeReadonly();
|
||||||
|
}
|
||||||
|
|
||||||
// Load many_many extraData for record.
|
// Load many_many extraData for record.
|
||||||
// Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields().
|
// Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields().
|
||||||
if($list instanceof ManyManyList) {
|
if($list instanceof ManyManyList) {
|
||||||
@ -429,6 +452,10 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
|||||||
$extraData = null;
|
$extraData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!$this->record->canEdit()) {
|
||||||
|
return $controller->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$form->saveInto($this->record);
|
$form->saveInto($this->record);
|
||||||
$this->record->write();
|
$this->record->write();
|
||||||
|
@ -192,6 +192,8 @@ class GridFieldDetailFormTest extends FunctionalTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCustomItemRequestClass() {
|
public function testCustomItemRequestClass() {
|
||||||
|
$this->logInWithPermission('ADMIN');
|
||||||
|
|
||||||
$component = new GridFieldDetailForm();
|
$component = new GridFieldDetailForm();
|
||||||
$this->assertEquals('GridFieldDetailForm_ItemRequest', $component->getItemRequestClass());
|
$this->assertEquals('GridFieldDetailForm_ItemRequest', $component->getItemRequestClass());
|
||||||
$component->setItemRequestClass('GridFieldDetailFormTest_ItemRequest');
|
$component->setItemRequestClass('GridFieldDetailFormTest_ItemRequest');
|
||||||
@ -199,6 +201,8 @@ class GridFieldDetailFormTest extends FunctionalTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testItemEditFormCallback() {
|
public function testItemEditFormCallback() {
|
||||||
|
$this->logInWithPermission('ADMIN');
|
||||||
|
|
||||||
$category = new GridFieldDetailFormTest_Category();
|
$category = new GridFieldDetailFormTest_Category();
|
||||||
$component = new GridFieldDetailForm();
|
$component = new GridFieldDetailForm();
|
||||||
$component->setItemEditFormCallback(function($form, $component) {
|
$component->setItemEditFormCallback(function($form, $component) {
|
||||||
|
Loading…
Reference in New Issue
Block a user