Skip to content

Commit

Permalink
BUG Fix SiteTree / SiteConfig permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Damian Mooyman committed Mar 19, 2015
1 parent a495385 commit 64955e5
Show file tree
Hide file tree
Showing 12 changed files with 708 additions and 343 deletions.
99 changes: 62 additions & 37 deletions code/controllers/CMSMain.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
'treeview',
'listview',
'ListViewForm',
'childfilter',
);

public function init() {
Expand Down Expand Up @@ -379,55 +380,49 @@ public function SiteTreeHints() {
$json = $cache->load($cacheKey);
if(!$json) {
$def['Root'] = array();
$def['Root']['disallowedParents'] = array();
$def['Root']['disallowedChildren'] = array();

// Contains all possible classes to support UI controls listing them all,
// such as the "add page here" context menu.
$def['All'] = array();

// Identify disallows and set globals
foreach($classes as $class) {
$obj = singleton($class);
if($obj instanceof HiddenClass) continue;

$allowedChildren = $obj->allowedChildren();

// SiteTree::allowedChildren() returns null rather than an empty array if SiteTree::allowed_chldren == 'none'
if($allowedChildren == null) $allowedChildren = array();

// Exclude SiteTree from possible Children
$possibleChildren = array_diff($allowedChildren, array("SiteTree"));

// Find i18n - names and build allowed children array
foreach($possibleChildren as $child) {
$instance = singleton($child);

if($instance instanceof HiddenClass) continue;

if(!array_key_exists($child, $cacheCanCreate) || !$cacheCanCreate[$child]) continue;
// Name item
$def['All'][$class] = array(
'title' => $obj->i18n_singular_name()
);

// skip this type if it is restricted
if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;
// Check if can be created at the root
$needsPerm = $obj->stat('need_permission');
if(
!$obj->stat('can_be_root')
|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
|| ($needsPerm && !$this->can($needsPerm))
) {
$def['Root']['disallowedChildren'][] = $class;
}

$title = $instance->i18n_singular_name();
// Hint data specific to the class
$def[$class] = array();

$def[$class]['allowedChildren'][] = array("ssclass" => $child, "ssname" => $title);
$defaultChild = $obj->defaultChild();
if($defaultChild !== 'Page' && $defaultChild !== null) {
$def[$class]['defaultChild'] = $defaultChild;
}

$allowedChildren = array_keys(array_diff($classes, $allowedChildren));
if($allowedChildren) $def[$class]['disallowedChildren'] = $allowedChildren;
$defaultChild = $obj->defaultChild();
if($defaultChild != 'Page' && $defaultChild != null) $def[$class]['defaultChild'] = $defaultChild;
$defaultParent = $obj->defaultParent();
$parent = SiteTree::get_by_link($defaultParent);
$id = $parent ? $parent->id : null;
if ($defaultParent != 1 && $defaultParent != null) $def[$class]['defaultParent'] = $defaultParent;
if(isset($def[$class]['disallowedChildren'])) {
foreach($def[$class]['disallowedChildren'] as $disallowedChild) {
$def[$disallowedChild]['disallowedParents'][] = $class;
}
if ($defaultParent !== 1 && $defaultParent !== null) {
$def[$class]['defaultParent'] = $defaultParent;
}

// Are any classes allowed to be parents of root?
$def['Root']['disallowedParents'][] = $class;
}

$json = Convert::raw2xml(Convert::raw2json($def));
$this->extend('updateSiteTreeHints', $def);

$json = Convert::raw2json($def);
$cache->save($json, $cacheKey);
}
return $json;
Expand Down Expand Up @@ -490,8 +485,6 @@ public function PageTypes() {

if($instance instanceof HiddenClass) continue;

if(!$instance->canCreate()) continue;

// skip this type if it is restricted
if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;

Expand Down Expand Up @@ -674,6 +667,38 @@ public function treeview($request) {
public function listview($request) {
return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
}

/**
* Callback to request the list of page types allowed under a given page instance.
* Provides a slower but more precise response over SiteTreeHints
*
* @param SS_HTTPRequest $request
* @return SS_HTTPResponse
*/
public function childfilter($request) {
// Check valid parent specified
$parentID = $request->requestVar('ParentID');
$parent = SiteTree::get()->byID($parentID);
if(!$parent || !$parent->exists()) return $this->httpError(404);

// Build hints specific to this class
// Identify disallows and set globals
$classes = SiteTree::page_type_classes();
$disallowedChildren = array();
foreach($classes as $class) {
$obj = singleton($class);
if($obj instanceof HiddenClass) continue;

if(!$obj->canCreate(null, array('Parent' => $parent))) {
$disallowedChildren[] = $class;
}
}

$this->extend('updateChildFilter', $disallowedChildren, $parentID);
$this->response->addHeader('Content-Type', 'application/json; charset=utf-8');
$this->response->setBody(Convert::raw2json($disallowedChildren));
return $this->response;
}

/**
* Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page
Expand Down
15 changes: 5 additions & 10 deletions code/controllers/CMSPageAddController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function AddForm() {
$pageTypes = array();
foreach($this->PageTypes() as $type) {
$html = sprintf('<span class="page-icon class-%s"></span><strong class="title">%s</strong><span class="description">%s</span>',
$type->getField('Title'),
$type->getField('ClassName'),
$type->getField('AddAction'),
$type->getField('Description')
);
Expand All @@ -39,9 +39,6 @@ public function AddForm() {
$childTitle = _t('CMSPageAddController.ParentMode_child', 'Under another page');

$fields = new FieldList(
// new HiddenField("ParentID", false, ($this->parentRecord) ? $this->parentRecord->ID : null),
// TODO Should be part of the form attribute, but not possible in current form API
$hintsField = new LiteralField('Hints', sprintf('<span class="hints" data-hints="%s"></span>', $this->SiteTreeHints())),
new LiteralField('PageModeHeader', sprintf($numericLabelTmpl, 1, _t('CMSMain.ChoosePageParentMode', 'Choose where to create this page'))),

$parentModeField = new SelectionGroup(
Expand Down Expand Up @@ -87,6 +84,8 @@ public function AddForm() {
$this->extend('updatePageOptions', $fields);

$form = new Form($this, "AddForm", $fields, $actions);
$form->setAttribute('data-hints', $this->SiteTreeHints());
$form->setAttribute('data-childfilter', $this->Link('childfilter'));
$form->addExtraClass('cms-add-form stacked cms-content center cms-edit-form ' . $this->BaseCSSClasses());
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));

Expand All @@ -113,12 +112,8 @@ public function doAdd($data, $form) {

if(!$parentObj || !$parentObj->ID) $parentID = 0;

if($parentObj) {
if(!$parentObj->canAddChildren()) return Security::permissionFailure($this);
if(!singleton($className)->canCreate()) return Security::permissionFailure($this);
} else {
if(!SiteConfig::current_site_config()->canCreateTopLevel())
return Security::permissionFailure($this);
if(!singleton($className)->canCreate(Member::currentUser(), array('Parent' => $parentObj))) {
return Security::permissionFailure($this);
}

$record = $this->getNewItem("new-$className-$parentID".$suffix, false);
Expand Down
86 changes: 64 additions & 22 deletions code/model/SiteConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,21 @@ class SiteConfig extends DataObject implements PermissionProvider {
"EditorGroups" => "Group",
"CreateTopLevelGroups" => "Group"
);

static $defaults = array(
"CanViewType" => "Anyone",
"CanEditType" => "LoggedInUsers",
"CanCreateTopLevelType" => "LoggedInUsers",
);

protected static $disabled_themes = array();

/**
* Default permission to check for 'LoggedInUsers' to create or edit pages
*
* @var array
*/
static $required_permission = array('CMS_ACCESS_CMSMain', 'CMS_ACCESS_LeftAndMain');

static public function disable_theme($theme) {
self::$disabled_themes[$theme] = $theme;
Expand Down Expand Up @@ -192,48 +205,70 @@ static public function make_site_config() {
* called if a page is set to Inherit, but there is nothing
* to inherit from.
*
* @param mixed $member
* @param Member $member
* @return boolean
*/
public function canView($member = null) {
public function canViewPages($member = null) {
if(!$member) $member = Member::currentUserID();
if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);

if ($member && Permission::checkMember($member, "ADMIN")) return true;

$extended = $this->extendedCan('canViewPages', $member);
if($extended !== null) return $extended;

if (!$this->CanViewType || $this->CanViewType == 'Anyone') return true;

// check for any logged-in users
if($this->CanViewType == 'LoggedInUsers' && $member) return true;
if($this->CanViewType === 'LoggedInUsers' && $member) return true;

// check for specific groups
if($this->CanViewType == 'OnlyTheseUsers' && $member && $member->inGroups($this->ViewerGroups())) return true;
if($this->CanViewType === 'OnlyTheseUsers' && $member && $member->inGroups($this->ViewerGroups())) return true;

return false;
}

/**
* Can a user edit pages on this site? This method is only
* called if a page is set to Inherit, but there is nothing
* to inherit from.
* to inherit from, or on new records without a parent.
*
* @param mixed $member
* @param Member $member
* @return boolean
*/
public function canEdit($member = null) {
public function canEditPages($member = null) {
if(!$member) $member = Member::currentUserID();
if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);

if ($member && Permission::checkMember($member, "ADMIN")) return true;

// check for any logged-in users
if(!$this->CanEditType || $this->CanEditType == 'LoggedInUsers' && $member) return true;
$extended = $this->extendedCan('canEditPages', $member);
if($extended !== null) return $extended;

// check for any logged-in users with CMS access
if( $this->CanEditType === 'LoggedInUsers'
&& Permission::checkMember($member, $this->config()->required_permission)
) {
return true;
}

// check for specific groups
if($this->CanEditType == 'OnlyTheseUsers' && $member && $member->inGroups($this->EditorGroups())) return true;
if($this->CanEditType === 'OnlyTheseUsers' && $member && $member->inGroups($this->EditorGroups())) {
return true;
}

return false;
}

public function canEdit($member = null) {
if(!$member) $member = Member::currentUserID();
if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);

$extended = $this->extendedCan('canEdit', $member);
if($extended !== null) return $extended;

return Permission::checkMember($member, "EDIT_SITECONFIG");
}

public function providePermissions() {
return array(
Expand All @@ -249,25 +284,32 @@ public function providePermissions() {
/**
* Can a user create pages in the root of this site?
*
* @param mixed $member
* @param Member $member
* @return boolean
*/
public function canCreateTopLevel($member = null) {
if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
$member = Member::currentUserID();
}

if (Permission::check('ADMIN')) return true;
if(!$member) $member = Member::currentUserID();
if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);

if ($member && Permission::checkMember($member, "ADMIN")) return true;

$extended = $this->extendedCan('canCreateTopLevel', $member);
if($extended !== null) return $extended;

// check for any logged-in users
if($this->CanCreateTopLevelType == 'LoggedInUsers' && $member) return true;
// check for any logged-in users with CMS permission
if( $this->CanCreateTopLevelType === 'LoggedInUsers'
&& Permission::checkMember($member, $this->config()->required_permission)
) {
return true;
}

// check for specific groups
if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);
if($this->CanCreateTopLevelType == 'OnlyTheseUsers' && $member && $member->inGroups($this->CreateTopLevelGroups())) return true;

if( $this->CanCreateTopLevelType === 'OnlyTheseUsers'
&& $member
&& $member->inGroups($this->CreateTopLevelGroups())
) {
return true;
}

return false;
}
Expand Down
Loading

0 comments on commit 64955e5

Please sign in to comment.