mirror of
https://github.com/silverstripe/silverstripe-dms
synced 2024-10-22 14:05:56 +02:00
Merge pull request #110 from fspringveldt/enhancement/92
Added ability to secure DMS assets by user group or role
This commit is contained in:
commit
a6aa575501
@ -2,6 +2,26 @@
|
||||
|
||||
/**
|
||||
* @package dms
|
||||
*
|
||||
* @property Varchar Filename
|
||||
* @property Varchar Folder
|
||||
* @property Varchar Title
|
||||
* @property Text Description
|
||||
* @property int ViewCount
|
||||
* @property DateTime LastChanged
|
||||
* @property Boolean EmbargoedIndefinitely
|
||||
* @property Boolean EmbargoedUntilPublished
|
||||
* @property DateTime EmbargoedUntilDate
|
||||
* @property DateTime ExpireAtDate
|
||||
* @property Enum DownloadBehavior
|
||||
* @property Enum CanViewType Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')
|
||||
* @property Enum CanEditType Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')
|
||||
*
|
||||
* @method ManyManyList RelatedDocuments
|
||||
* @method ManyManyList Tags
|
||||
* @method ManyManyList ViewerGroups
|
||||
* @method ManyManyList EditorGroups
|
||||
*
|
||||
*/
|
||||
class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
{
|
||||
@ -19,12 +39,16 @@ class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
"EmbargoedUntilDate" => 'SS_DateTime',
|
||||
"ExpireAtDate" => 'SS_DateTime',
|
||||
"DownloadBehavior" => 'Enum(array("open","download"), "download")',
|
||||
"CanViewType" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')",
|
||||
"CanEditType" => "Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')",
|
||||
);
|
||||
|
||||
private static $many_many = array(
|
||||
'Pages' => 'SiteTree',
|
||||
'RelatedDocuments' => 'DMSDocument',
|
||||
'Tags' => 'DMSTag'
|
||||
'Tags' => 'DMSTag',
|
||||
'ViewerGroups' => 'Group',
|
||||
'EditorGroups' => 'Group',
|
||||
);
|
||||
|
||||
private static $many_many_extraFields = array(
|
||||
@ -67,11 +91,6 @@ class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
*/
|
||||
private static $default_download_behaviour = 'download';
|
||||
|
||||
/**
|
||||
* @param Member $member
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canView($member = null)
|
||||
{
|
||||
if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
|
||||
@ -87,18 +106,36 @@ class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
}
|
||||
}
|
||||
|
||||
if ($member && $member->ID) {
|
||||
if (!$this->CanViewType || $this->CanViewType == 'Anyone') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($member && Permission::checkMember($member,
|
||||
array(
|
||||
'ADMIN',
|
||||
'SITETREE_EDIT_ALL',
|
||||
'SITETREE_VIEW_ALL',
|
||||
)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Member $member
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
if ($this->CanViewType == 'LoggedInUsers') {
|
||||
return $member && $member->exists();
|
||||
}
|
||||
|
||||
if ($this->CanViewType == 'OnlyTheseUsers' && $this->ViewerGroups()->count()) {
|
||||
return ($member && $member->inGroups($this->ViewerGroups()));
|
||||
}
|
||||
|
||||
return $this->canEdit($member);
|
||||
}
|
||||
|
||||
public function canEdit($member = null)
|
||||
{
|
||||
if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
|
||||
@ -113,7 +150,27 @@ class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
}
|
||||
}
|
||||
|
||||
return $this->canView();
|
||||
// Do early admin check
|
||||
if ($member && Permission::checkMember($member,
|
||||
array(
|
||||
'ADMIN',
|
||||
'SITETREE_EDIT_ALL',
|
||||
'SITETREE_VIEW_ALL',
|
||||
)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->CanEditType === 'LoggedInUsers') {
|
||||
return $member && $member->exists();
|
||||
}
|
||||
|
||||
if ($this->CanEditType === 'OnlyTheseUsers' && $this->EditorGroups()->count()) {
|
||||
return $member && $member->inGroups($this->EditorGroups());
|
||||
}
|
||||
|
||||
return ($member && Permission::checkMember($member, array('ADMIN', 'SITETREE_EDIT_ALL')));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,7 +192,7 @@ class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
}
|
||||
}
|
||||
|
||||
return $this->canView();
|
||||
return $this->canEdit($member);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -445,12 +502,30 @@ class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
|
||||
/**
|
||||
* Returns a link to download this document from the DMS store.
|
||||
* Alternatively a basic javascript alert will be shown should the user not have view permissions. An extension
|
||||
* point for this was also added.
|
||||
*
|
||||
* To extend use the following from within an Extension subclass:
|
||||
*
|
||||
* <code>
|
||||
* public function updateGetLink($result)
|
||||
* {
|
||||
* // Do something here
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLink()
|
||||
{
|
||||
return Controller::join_links(Director::baseURL(), 'dmsdocument/'.$this->ID);
|
||||
$result = Controller::join_links(Director::baseURL(), 'dmsdocument/' . $this->ID);
|
||||
if (!$this->canView()) {
|
||||
$result = sprintf("javascript:alert('%s')", $this->getPermissionDeniedReason());
|
||||
}
|
||||
|
||||
$this->extend('updateGetLink', $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1095,14 +1170,52 @@ class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
|
||||
$actionsPanel->setName("ActionsPanel");
|
||||
$actionsPanel->addExtraClass("DMSDocumentActionsPanel");
|
||||
|
||||
$fields->push($actionsPanel);
|
||||
|
||||
$this->addPermissionsFields($fields);
|
||||
$this->extend('updateCMSFields', $fields);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds permissions selection fields to the FieldList.
|
||||
*
|
||||
* @param FieldList $fields
|
||||
*/
|
||||
public function addPermissionsFields($fields)
|
||||
{
|
||||
$showFields = array(
|
||||
'CanViewType' => '',
|
||||
'ViewerGroups' => 'hide',
|
||||
'CanEditType' => '',
|
||||
'EditorGroups' => 'hide',
|
||||
);
|
||||
/** @var SiteTree $siteTree */
|
||||
$siteTree = singleton('SiteTree');
|
||||
$settingsFields = $siteTree->getSettingsFields();
|
||||
|
||||
foreach ($showFields as $name => $extraCss) {
|
||||
$compositeName = "Root.Settings.$name";
|
||||
/** @var FormField $field */
|
||||
if ($field = $settingsFields->fieldByName($compositeName)) {
|
||||
$field->addExtraClass($extraCss);
|
||||
$title = str_replace('page', 'document', $field->Title());
|
||||
$field->setTitle($title);
|
||||
|
||||
// Remove Inherited source option from DropdownField
|
||||
if ($field instanceof DropdownField) {
|
||||
$options = $field->getSource();
|
||||
unset($options['Inherit']);
|
||||
$field->setSource($options);
|
||||
}
|
||||
$fields->push($field);
|
||||
}
|
||||
}
|
||||
|
||||
$this->extend('updatePermissionsFields', $fields);
|
||||
}
|
||||
|
||||
public function onBeforeWrite()
|
||||
{
|
||||
parent::onBeforeWrite();
|
||||
@ -1329,4 +1442,55 @@ class DMSDocument extends DataObject implements DMSDocumentInterface
|
||||
|
||||
return $gridField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks at least one group is selected if CanViewType || CanEditType == 'OnlyTheseUsers'
|
||||
*
|
||||
* @return ValidationResult
|
||||
*/
|
||||
protected function validate()
|
||||
{
|
||||
$valid = parent::validate();
|
||||
|
||||
if ($this->CanViewType == 'OnlyTheseUsers' && !$this->ViewerGroups()->count()) {
|
||||
$valid->error(
|
||||
_t(
|
||||
'DMSDocument.VALIDATIONERROR_NOVIEWERSELECTED',
|
||||
"Selecting 'Only these people' from a viewers list needs at least one group selected."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->CanEditType == 'OnlyTheseUsers' && !$this->EditorGroups()->count()) {
|
||||
$valid->error(
|
||||
_t(
|
||||
'DMSDocument.VALIDATIONERROR_NOEDITORSELECTED',
|
||||
"Selecting 'Only these people' from a editors list needs at least one group selected."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reason as to why this document cannot be viewed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPermissionDeniedReason()
|
||||
{
|
||||
$result = '';
|
||||
|
||||
if ($this->CanViewType == 'LoggedInUsers') {
|
||||
$result = _t('DMSDocument.PERMISSIONDENIEDREASON_LOGINREQUIRED', 'Please log in to view this document');
|
||||
}
|
||||
|
||||
if ($this->CanViewType == 'OnlyTheseUsers') {
|
||||
$result = _t('DMSDocument.PERMISSIONDENIEDREASON_NOTAUTHORISED',
|
||||
'You are not authorised to view this document');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -54,39 +54,9 @@ class DMSDocument_Controller extends Controller
|
||||
{
|
||||
$doc = $this->getDocumentFromID($request);
|
||||
|
||||
|
||||
if (!empty($doc)) {
|
||||
$canView = false;
|
||||
|
||||
// Runs through all pages that this page links to and sets canView
|
||||
// to true if the user can view ONE of these pages
|
||||
if (method_exists($doc, 'Pages')) {
|
||||
$pages = $doc->Pages();
|
||||
if ($pages->Count() > 0) {
|
||||
foreach ($pages as $page) {
|
||||
if ($page->CanView()) {
|
||||
// just one canView is enough to know that we can
|
||||
// view the file
|
||||
$canView = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if the document isn't on any page, then allow viewing of
|
||||
// the document (because there is no canView() to consult)
|
||||
$canView = true;
|
||||
}
|
||||
}
|
||||
|
||||
// check for embargo or expiry
|
||||
if ($doc->isHidden()) {
|
||||
$canView = false;
|
||||
}
|
||||
|
||||
//admins can always download any document, even if otherwise hidden
|
||||
$member = Member::currentUser();
|
||||
if ($member && Permission::checkMember($member, 'ADMIN')) {
|
||||
$canView = true;
|
||||
}
|
||||
$canView = $doc->canView();
|
||||
|
||||
if ($canView) {
|
||||
$path = $doc->getFullPath();
|
||||
@ -129,8 +99,7 @@ class DMSDocument_Controller extends Controller
|
||||
//its ViewCount should be increased by 1 just before the browser sending the file to front.
|
||||
$doc->trackView();
|
||||
|
||||
$this->sendFile($path, $mime, $doc->getFilenameWithoutID(), $disposition);
|
||||
return;
|
||||
return $this->sendFile($path, $mime, $doc->getFilenameWithoutID(), $disposition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
(function ($) {
|
||||
(function($) {
|
||||
"use strict";
|
||||
|
||||
$.entwine('ss', function ($) {
|
||||
$.entwine('ss', function($) {
|
||||
|
||||
$('#DocumentTypeID ul li').entwine({
|
||||
onadd: function () {
|
||||
@ -11,7 +11,7 @@
|
||||
this.addClass('selected');
|
||||
}
|
||||
},
|
||||
onclick: function (e) {
|
||||
onclick: function(e) {
|
||||
$('#DocumentTypeID').find('li.selected').removeClass('selected');
|
||||
this.find('input').prop("checked", true);
|
||||
this.addClass('selected');
|
||||
@ -34,7 +34,22 @@
|
||||
}
|
||||
}
|
||||
});*/
|
||||
|
||||
$('#CanViewType input, #CanEditType input').entwine({
|
||||
onchange: function () {
|
||||
var dropDown = $(this).closest('.field').next();
|
||||
if ($(this).val() === 'OnlyTheseUsers') {
|
||||
dropDown.removeClass('hide');
|
||||
} else {
|
||||
dropDown.addClass('hide');
|
||||
}
|
||||
},
|
||||
onadd: function () {
|
||||
if ($(this).is(':checked') && $(this).val() === 'OnlyTheseUsers') {
|
||||
var dropDown = $(this).closest('.field').next();
|
||||
dropDown.removeClass('hide');
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#Actions ul li').entwine({
|
||||
onclick: function (e) {
|
||||
|
||||
@ -56,7 +71,6 @@
|
||||
|
||||
$('#Form_ItemEditForm_Embargo input, #Form_EditForm_Embargo input').entwine({
|
||||
onchange: function () {
|
||||
console.log('called');
|
||||
//selected the date options
|
||||
if (this.attr('value') === 'Date') {
|
||||
$('.embargoDatetime').children().show();
|
||||
|
@ -1,4 +1,4 @@
|
||||
<% if $isHidden != true %>
|
||||
<% if not $isHidden %>
|
||||
<div class="document $Extension">
|
||||
<% if $Title %>
|
||||
<h4><a href="$Link" title="<%t DMSDocument.DOWNLOAD "Download {title}" title=$Title %>">$Title</a></h4>
|
||||
|
@ -237,4 +237,103 @@ class DMSDocumentTest extends SapphireTest
|
||||
'Related documents GridField does not have an "add new" button'
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests whether the permissions fields are added
|
||||
*/
|
||||
public function testAddPermissionsFields()
|
||||
{
|
||||
$document = $this->objFromFixture('DMSDocument', 'd1');
|
||||
$fields = $document->getCMSFields();
|
||||
|
||||
$this->assertNotNull($fields->fieldByName('CanViewType'));
|
||||
$this->assertNotNull($fields->fieldByName('ViewerGroups'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test view permissions
|
||||
*/
|
||||
public function testCanView()
|
||||
{
|
||||
/** @var DMSDocument $document */
|
||||
$document = $this->objFromFixture('DMSDocument', 'doc-logged-in-users');
|
||||
// Make sure user is logged out
|
||||
if ($member = Member::currentUser()) {
|
||||
$member->logOut();
|
||||
}
|
||||
|
||||
// Logged out user test
|
||||
$this->assertFalse($document->canView());
|
||||
|
||||
// Logged in user test
|
||||
$adminID = $this->logInWithPermission();
|
||||
$admin = Member::get()->byID($adminID);
|
||||
$this->assertTrue($document->canView($admin));
|
||||
/** @var Member $member */
|
||||
$admin->logout();
|
||||
|
||||
// Check anyone
|
||||
$document = $this->objFromFixture('DMSDocument', 'doc-anyone');
|
||||
$this->assertTrue($document->canView());
|
||||
|
||||
// Check OnlyTheseUsers
|
||||
$document = $this->objFromFixture('DMSDocument', 'doc-only-these-users');
|
||||
$reportAdminID = $this->logInWithPermission('cable-guy');
|
||||
/** @var Member $reportAdmin */
|
||||
$reportAdmin = Member::get()->byID($reportAdminID);
|
||||
$this->assertFalse($document->canView($reportAdmin));
|
||||
// Add reportAdmin to group
|
||||
$reportAdmin->addToGroupByCode('content-author');
|
||||
$this->assertTrue($document->canView($reportAdmin));
|
||||
$reportAdmin->logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests edit permissions
|
||||
*/
|
||||
public function testCanEdit()
|
||||
{
|
||||
// Make sure user is logged out
|
||||
if ($member = Member::currentUser()) {
|
||||
$member->logOut();
|
||||
}
|
||||
|
||||
/** @var DMSDocument $document1 */
|
||||
$document1 = $this->objFromFixture('DMSDocument', 'doc-logged-in-users');
|
||||
|
||||
// Logged out user test
|
||||
$this->assertFalse($document1->canEdit());
|
||||
|
||||
//Logged in user test
|
||||
$contentAuthor = $this->objFromFixture('Member', 'editor');
|
||||
$this->assertTrue($document1->canEdit($contentAuthor));
|
||||
|
||||
// Check OnlyTheseUsers
|
||||
/** @var DMSDocument $document2 */
|
||||
$document2 = $this->objFromFixture('DMSDocument', 'doc-only-these-users');
|
||||
/** @var Member $cableGuy */
|
||||
$cableGuy = $this->objFromFixture('Member', 'non-editor');
|
||||
$this->assertFalse($document2->canEdit($cableGuy));
|
||||
|
||||
$cableGuy->addToGroupByCode('content-author');
|
||||
$this->assertTrue($document2->canEdit($cableGuy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test permission denied reasons for documents
|
||||
*/
|
||||
public function testGetPermissionDeniedReason()
|
||||
{
|
||||
/** @var DMSDocument $document1 */
|
||||
$doc1 = $this->objFromFixture('DMSDocument', 'doc-logged-in-users');
|
||||
$this->assertContains('Please log in to view this document', $doc1->getPermissionDeniedReason());
|
||||
|
||||
/** @var DMSDocument $doc2 */
|
||||
$doc2 = $this->objFromFixture('DMSDocument', 'doc-only-these-users');
|
||||
$this->assertContains('You are not authorised to view this document', $doc2->getPermissionDeniedReason());
|
||||
|
||||
/** @var DMSDocument $doc3 */
|
||||
$doc3 = $this->objFromFixture('DMSDocument', 'doc-anyone');
|
||||
$this->assertEquals('', $doc3->getPermissionDeniedReason());
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ class DMSEmbargoTest extends SapphireTest
|
||||
$doc = new DMSDocument();
|
||||
$doc->Filename = "DMS-test-lorum-file.pdf";
|
||||
$doc->Folder = "tests";
|
||||
$doc->CanViewType = 'LoggedInUsers';
|
||||
$docID = $doc->write();
|
||||
|
||||
//fake a request for a document
|
||||
|
@ -36,6 +36,13 @@ DMSTag:
|
||||
t6:
|
||||
Category: tag6
|
||||
Value: tag6value
|
||||
Group:
|
||||
content-author:
|
||||
Code: content-author
|
||||
Title: "Content Author"
|
||||
cable-guy:
|
||||
Code: cable-guy
|
||||
Title: "Cable Guy"
|
||||
DMSDocument:
|
||||
d1:
|
||||
Filename: test-file-file-doesnt-exist-1
|
||||
@ -51,3 +58,32 @@ DMSDocument:
|
||||
Filename: file-with-relations
|
||||
Folder: 5
|
||||
RelatedDocuments: =>DMSDocument.d1, =>DMSDocument.d2
|
||||
doc-logged-in-users:
|
||||
FileName: doc-logged-in-users
|
||||
CanViewType: LoggedInUsers
|
||||
CanEditType: LoggedInUsers
|
||||
Folder: 5
|
||||
Tags: =>DMSTag.t1, =>DMSTag.t2, =>DMSTag.t3, =>DMSTag.t4
|
||||
Pages: =>SiteTree.s1, =>SiteTree.s2, =>SiteTree.s3, =>SiteTree.s4, =>SiteTree.s5, =>SiteTree.s6
|
||||
doc-anyone:
|
||||
FileName: doc-anyone
|
||||
CanViewType: Anyone
|
||||
Folder: 5
|
||||
Tags: =>DMSTag.t1, =>DMSTag.t2, =>DMSTag.t3, =>DMSTag.t4
|
||||
Pages: =>SiteTree.s1, =>SiteTree.s2, =>SiteTree.s3, =>SiteTree.s4, =>SiteTree.s5, =>SiteTree.s6
|
||||
doc-only-these-users:
|
||||
FileName: doc-only-these-users
|
||||
CanViewType: OnlyTheseUsers
|
||||
CanEditType: OnlyTheseUsers
|
||||
Folder: 5
|
||||
Tags: =>DMSTag.t1, =>DMSTag.t2, =>DMSTag.t3, =>DMSTag.t4
|
||||
Pages: =>SiteTree.s1, =>SiteTree.s2, =>SiteTree.s3, =>SiteTree.s4, =>SiteTree.s5, =>SiteTree.s6
|
||||
ViewerGroups: =>Group.content-author
|
||||
EditorGroups: =>Group.content-author
|
||||
Member:
|
||||
editor:
|
||||
Name: editor
|
||||
Groups: =>Group.content-author
|
||||
non-editor:
|
||||
Name: cable-guy
|
||||
Groups: =>Group.cable-guy
|
||||
|
Loading…
Reference in New Issue
Block a user