Skip to content

Commit

Permalink
ENH add optional SiteConfig root level permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewandante committed Oct 20, 2023
1 parent 27000f1 commit e77c0d4
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 0 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"require": {
"php": "^8.1",
"silverstripe/framework": "^5.1",
"silverstripe/siteconfig": "^5.1",
"silverstripe/vendor-plugin": "^2",
"symfony/filesystem": "^6.1",
"intervention/image": "^2.7.2",
Expand Down
7 changes: 7 additions & 0 deletions src/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class File extends DataObject implements AssetContainer, Thumbnail, CMSPreviewab
* Permission for edit all files
*/
const EDIT_ALL = 'FILE_EDIT_ALL';
const GRANT_ACCESS = 'FILE_GRANT_ACCESS';

private static $default_sort = "\"Name\"";

Expand Down Expand Up @@ -1427,6 +1428,12 @@ public function providePermissions()
'category' => _t('SilverStripe\\Security\\Permission.CONTENT_CATEGORY', 'Content permissions'),
'sort' => -100,
'help' => _t(__CLASS__.'.EDIT_ALL_HELP', 'Edit any file on the site, even if restricted')
],
self::GRANT_ACCESS => [
'name' => _t(__CLASS__.'.GRANT_ACCESS_DESCRIPTION', 'Manage access rights for files'),
'category' => _t('SilverStripe\\Security\\Permission.CONTENT_CATEGORY', 'Content permissions'),
'sort' => -100,
'help' => _t(__CLASS__.'.GRANT_ACCESS_HELP', 'Allow setting of file-specific access restrictions in the "Files" section')
]
];
}
Expand Down
161 changes: 161 additions & 0 deletions src/RootLevelAccessSiteConfigExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

namespace SilverStripe\Assets;

use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\ListboxField;
use SilverStripe\Forms\OptionsetField;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Security\Group;
use SilverStripe\Security\InheritedPermissions;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;

class RootLevelAccessSiteConfigExtension extends DataExtension
{
private static $db = [
"RootAssetsCanViewType" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers, OnlyTheseMembers', 'Anyone')",
"RootAssetsCanEditType" => "Enum('LoggedInUsers, OnlyTheseUsers, OnlyTheseMembers', 'OnlyTheseUsers')",
];

private static $many_many = [
"RootAssetsViewerGroups" => Group::class,
"RootAssetsEditorGroups" => Group::class,
"RootAssetsViewerMembers" => Member::class,
"RootAssetsEditorMembers" => Member::class,
];

private static $defaults = [
"RootAssetsCanViewType" => "Anyone",
"RootAssetsCanEditType" => "OnlyTheseUsers",
];

public function requireDefaultRecords()
{
parent::requireDefaultRecords();
if ($this->getOwner()->RootAssetsEditorGroups()->count() < 1) {
$groups = Permission::get_groups_by_permission(File::EDIT_ALL);
foreach ($groups as $group) {
$this->getOwner()->RootAssetsEditorGroups()->add($group);
}
}
}

public function updateCMSFields(FieldList $fields)
{
// Lots of logic borrowed from SiteConfig::getCMSFields()
$mapFn = function ($groups = []) {
$map = [];
foreach ($groups as $group) {
// Listboxfield values are escaped, use ASCII char instead of &raquo;
$map[$group->ID] = $group->getBreadcrumbs(' > ');
}
asort($map);
return $map;
};
$groupsMap = $mapFn(Group::get());
$membersMap = Member::get()->map('ID', 'Name');
$editAllGroupsMap = $mapFn(Permission::get_groups_by_permission([File::EDIT_ALL, 'ADMIN']));

$fields->addFieldsToTab(
'Root.Access',
[
$viewersOptionsField = OptionsetField::create(
"RootAssetsCanViewType",
_t(self::class . '.ROOTASSETSVIEWHEADER', "Who can view files on this site?")
),
$viewerGroupsField = ListboxField::create(
"RootAssetsViewerGroups",
_t('SilverStripe\\CMS\\Model\\SiteTree.ROOTASSETSVIEWERGROUPS', "File Viewer Groups")
)
->setSource($groupsMap)
->setAttribute(
'data-placeholder',
_t('SilverStripe\\CMS\\Model\\SiteTree.GroupPlaceholder', 'Click to select group')
),
$viewerMembersField = ListboxField::create(
"RootAssetsViewerMembers",
_t(__CLASS__.'.ROOTASSETSVIEWERMEMBERS', "Viewer Users"),
$membersMap,
),
$editorsOptionsField = OptionsetField::create(
"RootAssetsCanEditType",
_t(self::class . '.ROOTASSETSEDITHEADER', "Who can edit files on this site?")
),
$editorGroupsField = ListboxField::create(
"RootAssetsEditorGroups",
_t('SilverStripe\\CMS\\Model\\SiteTree.EDITORGROUPS', "File Editor Groups")
)
->setSource($groupsMap)
->setAttribute(
'data-placeholder',
_t('SilverStripe\\CMS\\Model\\SiteTree.GroupPlaceholder', 'Click to select group')
),
$editorMembersField = ListboxField::create(
"RootAssetsEditorMembers",
_t(__CLASS__.'.ROOTASSETSEDITORMEMBERS', "Editor Users"),
$membersMap
)
]
);

$viewersOptionsSource = [];
$viewersOptionsSource[InheritedPermissions::ANYONE] = _t('SilverStripe\\CMS\\Model\\SiteTree.ACCESSANYONE', "Anyone");
$viewersOptionsSource[InheritedPermissions::LOGGED_IN_USERS] = _t(
'SilverStripe\\CMS\\Model\\SiteTree.ACCESSLOGGEDIN',
"Logged-in users"
);
$viewersOptionsSource[InheritedPermissions::ONLY_THESE_USERS] = _t(
'SilverStripe\\CMS\\Model\\SiteTree.ACCESSONLYTHESE',
"Only these groups (choose from list)"
);
$viewersOptionsSource[InheritedPermissions::ONLY_THESE_MEMBERS] = _t(
'SilverStripe\\CMS\\Model\\SiteTree.ACCESSONLYMEMBERS',
"Only these users (choose from list)"
);
$viewersOptionsField->setSource($viewersOptionsSource);
$editorsOptionsSource = $viewersOptionsSource;
unset($editorsOptionsSource[InheritedPermissions::ANYONE]);
$editorsOptionsField->setSource($editorsOptionsSource);


if ($editAllGroupsMap) {
$viewerGroupsField->setDescription(_t(
'SilverStripe\\CMS\\Model\\SiteTree.ROOT_ASSETS_VIEWER_GROUPS_FIELD_DESC',
'Groups with global view permissions: {groupList}',
['groupList' => implode(', ', array_values($editAllGroupsMap))]
));
$editorGroupsField->setDescription(_t(
'SilverStripe\\CMS\\Model\\SiteTree.ROOT_ASSETS_EDITOR_GROUPS_FIELD_DESC',
'Groups with global edit permissions: {groupList}',
['groupList' => implode(', ', array_values($editAllGroupsMap))]
));
}

if (!Permission::check(File::GRANT_ACCESS)) {
$fields->makeFieldReadonly($viewersOptionsField);
if ($this->getOwner()->RootAssetsCanViewType === InheritedPermissions::ONLY_THESE_USERS) {
$fields->makeFieldReadonly($viewerGroupsField);
$fields->removeByName('RootAssetsViewerMembers');
} elseif ($this->getOwner()->RootAssetsCanViewType === InheritedPermissions::ONLY_THESE_MEMBERS) {
$fields->makeFieldReadonly($viewerMembersField);
$fields->removeByName('RootAssetsViewerGroups');
} else {
$fields->removeByName('RootAssetsViewerGroups');
$fields->removeByName('RootAssetsViewerMembers');
}

$fields->makeFieldReadonly($editorsOptionsField);
if ($this->getOwner()->RootAssetsCanEditType === InheritedPermissions::ONLY_THESE_USERS) {
$fields->makeFieldReadonly($editorGroupsField);
$fields->removeByName('RootAssetsEditorMembers');
} elseif ($this->getOwner()->RootAssetsCanEditType === InheritedPermissions::ONLY_THESE_MEMBERS) {
$fields->makeFieldReadonly($editorMembersField);
$fields->removeByName('RootAssetsEditorGroups');
} else {
$fields->removeByName('RootAssetsEditorGroups');
$fields->removeByName('RootAssetsEditorMembers');
}
}
}
}
131 changes: 131 additions & 0 deletions src/SiteConfigFilePermissions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

namespace SilverStripe\Assets;

use SilverStripe\Assets\File;
use SilverStripe\Security\DefaultPermissionChecker;
use SilverStripe\Security\InheritedPermissions;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
use SilverStripe\SiteConfig\SiteConfig;

/**
* Permissions for root files with Can*Type = Inherit
*/
class SiteConfigFilePermissions implements DefaultPermissionChecker
{
/**
* Can root be edited?
*
* @param Member $member
* @return bool
*/
public function canEdit(Member $member = null)
{
if (!$member) {
$member = Security::getCurrentUser();
}

if (Permission::checkMember($member, 'ADMIN') || Permission::check($member, File::EDIT_ALL)) {
return true;
}

$siteConfig = SiteConfig::current_site_config();
$canEditType = $siteConfig->RootAssetsCanEditType;

// Any logged in user can edit root files and folders
if ($canEditType === InheritedPermissions::LOGGED_IN_USERS) {
return $member !== null;
}

// Specific user groups can edit root files and folders
if ($canEditType === InheritedPermissions::ONLY_THESE_USERS) {
if (!$member) {
return false;
}
return $member->inGroups($siteConfig->RootAssetsEditorGroups());
}

// Specific users can edit root files and folders
if ($canEditType === InheritedPermissions::ONLY_THESE_MEMBERS) {
if (!$member) {
return false;
}
return $siteConfig->RootAssetsEditorMembers()->filter('ID', $member->ID)->count() > 0;
}

// Secure by default
return false;
}

/**
* Can root be viewed?
*
* @param Member $member
* @return bool
*/
public function canView($member = null)
{
if (!$member) {
$member = Security::getCurrentUser();
}

if (Permission::checkMember($member, 'ADMIN') || Permission::check($member, File::EDIT_ALL)) {
return true;
}

$siteConfig = SiteConfig::current_site_config();
$canViewType = $siteConfig->RootAssetsCanViewType;

if ($canViewType === InheritedPermissions::ANYONE) {
return true;
}

// Any logged in user can view root files and folders
if ($canViewType === InheritedPermissions::LOGGED_IN_USERS) {
return $member !== null;
}

// Specific user groups can view root files and folders
if ($canViewType === InheritedPermissions::ONLY_THESE_USERS) {
if (!$member) {
return false;
}
return $member->inGroups($siteConfig->RootAssetsViewerGroups());
}

// Specific users can view root files and folders
if ($canViewType === InheritedPermissions::ONLY_THESE_MEMBERS) {
if (!$member) {
return false;
}
return $siteConfig->RootAssetsViewerMembers()->filter('ID', $member->ID)->count() > 0;
}

// Secure by default
return false;
}

/**
* Can root be deleted?
*
* @param Member $member
* @return bool
*/
public function canDelete(Member $member = null)
{
return $this->canEdit($member);
}

/**
* Can root objects be created?
*
* @param Member $member
* @return bool
*/
public function canCreate(Member $member = null)
{
return $this->canEdit($member);
}
}

0 comments on commit e77c0d4

Please sign in to comment.