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
|
||||
|
||||
* `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.
|
||||
|
||||
## 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
|
||||
|
||||
* [ModelAdmin: A UI driven by GridField](/reference/modeladmin)
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* 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
|
||||
* @subpackage gridfield
|
||||
@ -21,9 +22,12 @@ class GridFieldAddNewButton implements GridField_HTMLProvider {
|
||||
}
|
||||
|
||||
public function getHTMLFragments($gridField) {
|
||||
$singleton = singleton($gridField->getModelClass());
|
||||
if(!$singleton->canCreate()) return array();
|
||||
|
||||
if(!$this->buttonName) {
|
||||
// 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));
|
||||
}
|
||||
|
||||
|
@ -98,15 +98,16 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
|
||||
*/
|
||||
public function getColumnContent($gridField, $record, $columnName) {
|
||||
if($this->removeRelation) {
|
||||
if(!$record->canEdit()) return;
|
||||
|
||||
$field = GridField_FormAction::create($gridField, 'UnlinkRelation'.$record->ID, false,
|
||||
"unlinkrelation", array('RecordID' => $record->ID))
|
||||
->addExtraClass('gridfield-button-unlink')
|
||||
->setAttribute('title', _t('GridAction.UnlinkRelation', "Unlink"))
|
||||
->setAttribute('data-icon', 'chain--minus');
|
||||
} else {
|
||||
if(!$record->canDelete()) {
|
||||
return;
|
||||
}
|
||||
if(!$record->canDelete()) return;
|
||||
|
||||
$field = GridField_FormAction::create($gridField, 'DeleteRecord'.$record->ID, false, "deleterecord",
|
||||
array('RecordID' => $record->ID))
|
||||
->addExtraClass('gridfield-button-delete')
|
||||
@ -132,13 +133,20 @@ class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_Actio
|
||||
if(!$item) {
|
||||
return;
|
||||
}
|
||||
if($actionName == 'deleterecord' && !$item->canDelete()) {
|
||||
throw new ValidationException(
|
||||
_t('GridFieldAction_Delete.DeletePermissionsFailure',"No delete permissions"),0);
|
||||
}
|
||||
|
||||
if($actionName == 'deleterecord') {
|
||||
if(!$item->canDelete()) {
|
||||
throw new ValidationException(
|
||||
_t('GridFieldAction_Delete.DeletePermissionsFailure',"No delete permissions"),0);
|
||||
}
|
||||
|
||||
$item->delete();
|
||||
} else {
|
||||
if(!$item->canEdit()) {
|
||||
throw new ValidationException(
|
||||
_t('GridFieldAction_Delete.EditPermissionsFailure',"No permission to unlink record"),0);
|
||||
}
|
||||
|
||||
$gridField->getList()->remove($item);
|
||||
}
|
||||
}
|
||||
|
@ -310,16 +310,31 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
||||
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();
|
||||
if($this->record->ID !== 0) {
|
||||
$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Save', 'Save'))
|
||||
->setUseButtonTag(true)
|
||||
->addExtraClass('ss-ui-action-constructive')
|
||||
->setAttribute('data-icon', 'accept'));
|
||||
if($canEdit) {
|
||||
$actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Save', 'Save'))
|
||||
->setUseButtonTag(true)
|
||||
->addExtraClass('ss-ui-action-constructive')
|
||||
->setAttribute('data-icon', 'accept'));
|
||||
}
|
||||
|
||||
$actions->push(FormAction::create('doDelete', _t('GridFieldDetailForm.Delete', 'Delete'))
|
||||
->setUseButtonTag(true)
|
||||
->addExtraClass('ss-ui-action-destructive'));
|
||||
if($canDelete) {
|
||||
$actions->push(FormAction::create('doDelete', _t('GridFieldDetailForm.Delete', 'Delete'))
|
||||
->setUseButtonTag(true)
|
||||
->addExtraClass('ss-ui-action-destructive'));
|
||||
}
|
||||
|
||||
}else{ // adding new record
|
||||
//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);
|
||||
|
||||
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.
|
||||
// Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields().
|
||||
if($list instanceof ManyManyList) {
|
||||
@ -429,6 +452,10 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
|
||||
$extraData = null;
|
||||
}
|
||||
|
||||
if(!$this->record->canEdit()) {
|
||||
return $controller->httpError(403);
|
||||
}
|
||||
|
||||
try {
|
||||
$form->saveInto($this->record);
|
||||
$this->record->write();
|
||||
|
@ -192,6 +192,8 @@ class GridFieldDetailFormTest extends FunctionalTest {
|
||||
}
|
||||
|
||||
public function testCustomItemRequestClass() {
|
||||
$this->logInWithPermission('ADMIN');
|
||||
|
||||
$component = new GridFieldDetailForm();
|
||||
$this->assertEquals('GridFieldDetailForm_ItemRequest', $component->getItemRequestClass());
|
||||
$component->setItemRequestClass('GridFieldDetailFormTest_ItemRequest');
|
||||
@ -199,6 +201,8 @@ class GridFieldDetailFormTest extends FunctionalTest {
|
||||
}
|
||||
|
||||
public function testItemEditFormCallback() {
|
||||
$this->logInWithPermission('ADMIN');
|
||||
|
||||
$category = new GridFieldDetailFormTest_Category();
|
||||
$component = new GridFieldDetailForm();
|
||||
$component->setItemEditFormCallback(function($form, $component) {
|
||||
|
Loading…
Reference in New Issue
Block a user