allow admin to disable sharing for specific groups of users

This commit is contained in:
Bjoern Schiessle 2014-05-13 15:22:18 +02:00
parent 14a953fbe0
commit 12338e0ef0
20 changed files with 352 additions and 23 deletions

View File

@ -358,6 +358,10 @@ table td.filename form { font-size:14px; margin-left:48px; margin-right:48px; }
padding: 28px 14px 19px !important;
}
#fileList .action.action-share-notification span, img, a {
cursor: default !important;
}
a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; }
/* Actions for selected files */

View File

@ -27,13 +27,29 @@ $(document).ready(function() {
}
$('#fileList').on('fileActionsReady',function(){
var $fileList = $(this);
var allShared = $fileList.find('[data-share-owner] [data-Action="Share"]');
allShared.addClass('permanent');
allShared.find('span').text(function(){
var $owner = $(this).closest('tr').attr('data-share-owner');
return ' ' + t('files_sharing', 'Shared by {owner}', {owner: $owner});
});
// if no share action exists because the admin disabled sharing for this user
// we create a share notification action to inform the user about files
// shared with him otherwise we just update the existing share action.
var allShared;
if (oc_appconfig.core.sharingDisabledForUser) {
var $fileList = $(this);
allShared = $fileList.find('[data-share-owner]');
var shareNotification = '<a class="action action-share-notification permanent"' +
' data-action="Share-Notification" href="#" original-title="">' +
' <img class="svg" src="' + OC.imagePath('core', 'actions/share') + '"></img>';
$(allShared).find('.fileactions').append(function() {
var owner = $(this).closest('tr').attr('data-share-owner');
var shareBy = t('files_sharing', 'Shared by {owner}', {owner: owner});
return shareNotification + '<span> ' + shareBy + '</span></span>';
});
} else {
allShared = $fileList.find('[data-share-owner] [data-Action="Share"]');
allShared.addClass('permanent');
allShared.find('span').text(function(){
var $owner = $(this).closest('tr').attr('data-share-owner');
return ' ' + t('files_sharing', 'Shared by {owner}', {owner: $owner});
});
}
// FIXME: these calls are also working on hard-coded
// list selectors...
@ -48,7 +64,7 @@ $(document).ready(function() {
}
});
FileActions.register('all', 'Share', OC.PERMISSION_READ, OC.imagePath('core', 'actions/share'), function(filename) {
FileActions.register('all', 'Share', OC.PERMISSION_SHARE, OC.imagePath('core', 'actions/share'), function(filename) {
var tr = FileList.findFileEl(filename);
var itemType = 'file';
if ($(tr).data('type') == 'dir') {

View File

@ -30,6 +30,7 @@ class Shared_Permissions extends Permissions {
* @return int (-1 if file no permissions set)
*/
public function get($fileId, $user) {
if ($fileId == -1) {
// if we ask for the mount point return -1 so that we can get the correct
// permissions by the path, with the root fileId we have no idea which share is meant
@ -37,11 +38,14 @@ class Shared_Permissions extends Permissions {
}
$source = \OCP\Share::getItemSharedWithBySource('file', $fileId, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE,
null, true);
$permission = -1;
if ($source) {
return $source['permissions'];
} else {
return -1;
$permission = $this->updatePermissions($source['permissions']);
}
return $permission;
}
/**
@ -55,7 +59,7 @@ class Shared_Permissions extends Permissions {
$source = \OCP\Share::getItemSharedWithBySource('file', $fileId, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE,
null, false);
if ($source) {
return $source['permissions'];
return $this->updatePermissions($source['permissions']);
} else {
return -1;
}
@ -106,7 +110,7 @@ class Shared_Permissions extends Permissions {
$result = $query->execute(array($parentId));
$filePermissions = array();
while ($row = $result->fetchRow()) {
$filePermissions[$row['fileid']] = $permissions;
$filePermissions[$row['fileid']] = $this->updatePermissions($permissions);
}
return $filePermissions;
}

View File

@ -108,6 +108,11 @@ class Shared extends \OC\Files\Storage\Common {
if (pathinfo($target, PATHINFO_EXTENSION) === 'part') {
$permissions |= \OCP\PERMISSION_DELETE;
}
if (\OC_Util::isSharingDisabledForUser()) {
$permissions &= ~\OCP\PERMISSION_SHARE;
}
return $permissions;
}
@ -198,6 +203,9 @@ class Shared extends \OC\Files\Storage\Common {
}
public function isSharable($path) {
if (\OCP\Util::isSharingDisabledForUser()) {
return false;
}
return ($this->getPermissions($path) & \OCP\PERMISSION_SHARE);
}

View File

@ -171,6 +171,78 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base {
$appConfig->setValue('core', 'shareapi_enforce_links_password', 'no');
}
/**
* @medium
*/
function testSharePermissions() {
// sharing file to a user should work if shareapi_exclude_groups is set
// to no
\OC_Appconfig::setValue('core', 'shareapi_exclude_groups', 'no');
$_POST['path'] = $this->filename;
$_POST['shareWith'] = \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2;
$_POST['shareType'] = \OCP\Share::SHARE_TYPE_USER;
$result = Share\Api::createShare(array());
$this->assertTrue($result->succeeded());
$data = $result->getData();
$share = $this->getShareFromId($data['id']);
$items = \OCP\Share::getItemShared('file', $share['item_source']);
$this->assertTrue(!empty($items));
$fileinfo = $this->view->getFileInfo($this->filename);
$result = \OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2);
$this->assertTrue($result);
// exclude groups, but not the group the user belongs to. Sharing should still work
\OC_Appconfig::setValue('core', 'shareapi_exclude_groups', 'yes');
\OC_Appconfig::setValue('core', 'shareapi_exclude_groups_list', 'admin,group1,group2');
$_POST['path'] = $this->filename;
$_POST['shareWith'] = \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2;
$_POST['shareType'] = \OCP\Share::SHARE_TYPE_USER;
$result = Share\Api::createShare(array());
$this->assertTrue($result->succeeded());
$data = $result->getData();
$share = $this->getShareFromId($data['id']);
$items = \OCP\Share::getItemShared('file', $share['item_source']);
$this->assertTrue(!empty($items));
$fileinfo = $this->view->getFileInfo($this->filename);
$result = \OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2);
$this->assertTrue($result);
// now we exclude the group the user belongs to ('group'), sharing should fail now
\OC_Appconfig::setValue('core', 'shareapi_exclude_groups_list', 'admin,group');
$_POST['path'] = $this->filename;
$_POST['shareWith'] = \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2;
$_POST['shareType'] = \OCP\Share::SHARE_TYPE_USER;
$result = Share\Api::createShare(array());
$this->assertFalse($result->succeeded());
// cleanup
\OC_Appconfig::setValue('core', 'shareapi_exclude_groups', 'no');
\OC_Appconfig::setValue('core', 'shareapi_exclude_groups_list', '');
}
/**
* @medium

View File

@ -109,6 +109,8 @@ abstract class Test_Files_Sharing_Base extends \PHPUnit_Framework_TestCase {
if ($create) {
\OC_User::createUser($user, $password);
\OC_Group::createGroup('group');
\OC_Group::addToGroup($user, 'group');
}
\OC_Util::tearDownFS();

View File

@ -25,9 +25,9 @@ require_once __DIR__ . '/base.php';
use OCA\Files\Share;
/**
* Class Test_Files_Sharing_Api
* Class Test_Files_Sharing_Proxy
*/
class Test_Files_Sharing_Api extends Test_Files_Sharing_Base {
class Test_Files_Sharing_Proxy extends Test_Files_Sharing_Base {
const TEST_FOLDER_NAME = '/folder_share_api_test';

View File

@ -80,6 +80,7 @@ $array = array(
'defaultExpireDate' => $defaultExpireDate,
'defaultExpireDateEnforced' => $enforceDefaultExpireDate,
'enforcePasswordForPublicLink' => \OCP\Util::isPublicLinkPasswordRequired(),
'sharingDisabledForUser' => \OCP\Util::isSharingDisabledForUser(),
)
)
),

View File

@ -36,7 +36,7 @@ class Permissions {
$sql = 'SELECT `permissions` FROM `*PREFIX*permissions` WHERE `user` = ? AND `fileid` = ?';
$result = \OC_DB::executeAudited($sql, array($user, $fileId));
if ($row = $result->fetchRow()) {
return $row['permissions'];
return $this->updatePermissions($row['permissions']);
} else {
return -1;
}
@ -78,7 +78,7 @@ class Permissions {
$result = \OC_DB::executeAudited($sql, $params);
$filePermissions = array();
while ($row = $result->fetchRow()) {
$filePermissions[$row['fileid']] = $row['permissions'];
$filePermissions[$row['fileid']] = $this->updatePermissions($row['permissions']);
}
return $filePermissions;
}
@ -99,7 +99,7 @@ class Permissions {
$result = \OC_DB::executeAudited($sql, array($parentId, $user));
$filePermissions = array();
while ($row = $result->fetchRow()) {
$filePermissions[$row['fileid']] = $row['permissions'];
$filePermissions[$row['fileid']] = $this->updatePermissions($row['permissions']);
}
return $filePermissions;
}
@ -140,4 +140,17 @@ class Permissions {
}
return $users;
}
/**
* check if admin removed the share permission for the user and update the permissions
*
* @param int $permissions
* @return int
*/
protected function updatePermissions($permissions) {
if (\OCP\Util::isSharingDisabledForUser()) {
$permissions &= ~\OCP\PERMISSION_SHARE;
}
return $permissions;
}
}

View File

@ -81,6 +81,10 @@ abstract class Common implements \OC\Files\Storage\Storage {
}
public function isSharable($path) {
if (\OC_Util::isSharingDisabledForUser()) {
return false;
}
return $this->isReadable($path);
}

View File

@ -485,15 +485,23 @@ class Share extends \OC\Share\Constants {
$itemSourceName = $itemSource;
}
// verify that the file exists before we try to share it
// check if file can be shared
if ($itemType === 'file' or $itemType === 'folder') {
$path = \OC\Files\Filesystem::getPath($itemSource);
// verify that the file exists before we try to share it
if (!$path) {
$message = 'Sharing %s failed, because the file does not exist';
$message_t = $l->t('Sharing %s failed, because the file does not exist', array($itemSourceName));
\OC_Log::write('OCP\Share', sprintf($message, $itemSourceName), \OC_Log::ERROR);
throw new \Exception($message_t);
}
// verify that the user has share permission
if (!\OC\Files\Filesystem::isSharable($path)) {
$message = 'You are not allowed to share %s';
$message_t = $l->t('You are not allowed to share %s', array($itemSourceName));
\OC_Log::write('OCP\Share', sprintf($message, $itemSourceName), \OC_Log::ERROR);
throw new \Exception($message_t);
}
}
//verify that we don't share a folder which already contains a share mount point

View File

@ -96,6 +96,29 @@ class OC_Util {
return ($enforcePassword === 'yes') ? true : false;
}
/**
* check if sharing is disabled for the current user
*
* @return boolean
*/
public static function isSharingDisabledForUser() {
if (\OC_Appconfig::getValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
$user = \OCP\User::getUser();
$groupsList = \OC_Appconfig::getValue('core', 'shareapi_exclude_groups_list', '');
$excludedGroups = explode(',', $groupsList);
$usersGroups = \OC_Group::getUserGroups($user);
if (!empty($usersGroups)) {
$remainingGroups = array_diff($usersGroups, $excludedGroups);
// if the user is only in groups which are disabled for sharing then
// sharing is also disabled for the user
if (empty($remainingGroups)) {
return true;
}
}
}
return false;
}
/**
* Get the quota of a user
* @param string $user

View File

@ -116,6 +116,15 @@ class Util {
}
}
/**
* check if sharing is disabled for the current user
*
* @return boolean
*/
public static function isSharingDisabledForUser() {
return \OC_Util::isSharingDisabledForUser();
}
/**
* get l10n object
* @param string $application

View File

@ -10,6 +10,7 @@ OC_Util::checkAdminUser();
OC_Util::addStyle( "settings", "settings" );
OC_Util::addScript( "settings", "admin" );
OC_Util::addScript( "settings", "log" );
OC_Util::addScript( 'core', 'multiselect' );
OC_App::setActiveNavigationEntry( "admin" );
$tmpl = new OC_Template( 'settings', 'admin', 'user');
@ -48,6 +49,23 @@ $tmpl->assign('shareAPIEnabled', OC_Appconfig::getValue('core', 'shareapi_enable
$tmpl->assign('shareDefaultExpireDateSet', OC_Appconfig::getValue('core', 'shareapi_default_expire_date', 'no'));
$tmpl->assign('shareExpireAfterNDays', OC_Appconfig::getValue('core', 'shareapi_expire_after_n_days', '7'));
$tmpl->assign('shareEnforceExpireDate', OC_Appconfig::getValue('core', 'shareapi_enforce_expire_date', 'no'));
$excludeGroups = OC_Appconfig::getValue('core', 'shareapi_exclude_groups', 'no') === 'yes' ? true : false;
$tmpl->assign('shareExcludeGroups', $excludeGroups);
$allGroups = OC_Group::getGroups();
$excludedGroupsList = OC_Appconfig::getValue('core', 'shareapi_exclude_groups_list', '');
$excludedGroups = $excludedGroupsList !== '' ? explode(',', $excludedGroupsList) : array();
$groups = array();
foreach ($allGroups as $group) {
if (in_array($group, $excludedGroups)) {
$groups[$group] = array('gid' => $group,
'excluded' => true);
} else {
$groups[$group] = array('gid' => $group,
'excluded' => false);
}
}
ksort($groups);
$tmpl->assign('groups', $groups);
// Check if connected using HTTPS

View File

@ -0,0 +1,18 @@
<?php
OC_JSON::checkSubAdminUser();
OCP\JSON::callCheck();
$selectedGroups = isset($_POST["selectedGroups"]) ? json_decode($_POST["selectedGroups"]) : array();
$changedGroup = isset($_POST["changedGroup"]) ? $_POST["changedGroup"] : '';
if ($changedGroup !== '') {
if(($key = array_search($changedGroup, $selectedGroups)) !== false) {
unset($selectedGroups[$key]);
} else {
$selectedGroups[] = $changedGroup;
}
} else {
\OCP\Util::writeLog('core', 'Can not update list of excluded groups from sharing, parameter missing', \OCP\Util::WARN);
}
\OC_Appconfig::setValue('core', 'shareapi_exclude_groups_list', implode(',', $selectedGroups));

View File

@ -158,6 +158,15 @@ table.shareAPI .indent { padding-left: 2em; }
vertical-align: text-bottom;
}
#selectGroups select {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: inline-block;
height: 36px;
padding: 7px 10px
}
span.success {
background: #37ce02;
border-radius: 8px;

View File

@ -1,4 +1,49 @@
var SharingGroupList = {
applyMultipleSelect: function(element) {
var checked = [];
if ($(element).hasClass('groupsselect')) {
if (element.data('userGroups')) {
checked = element.data('userGroups');
}
var checkHandeler = function(group) {
$.post(OC.filePath('settings', 'ajax', 'excludegroups.php'),
{changedGroup: group, selectedGroups: JSON.stringify(checked)},
function() {});
};
var addGroup = function(select, group) {
$(this).each(function(index, element) {
if ($(element).find('option[value="' + group + '"]').length === 0 &&
select.data('msid') !== $(element).data('msid')) {
$(element).append('<option value="' + escapeHTML(group) + '">' +
escapeHTML(group) + '</option>');
}
});
};
var label = null;
element.multiSelect({
createCallback: addGroup,
createText: label,
selectedFirst: true,
checked: checked,
oncheck: checkHandeler,
onuncheck: checkHandeler,
minWidth: 100
});
}
}
};
$(document).ready(function(){
$('select#excludedGroups[multiple]').each(function (index, element) {
SharingGroupList.applyMultipleSelect($(element));
});
$('#loglevel').change(function(){
$.post(OC.filePath('settings','ajax','setloglevel.php'), { level: $(this).val() },function(){
OC.Log.reload();
@ -84,4 +129,8 @@ $(document).ready(function(){
OC.msg.finishedAction('#sendtestmail_msg', data);
});
});
$('#shareapiExcludeGroups').change(function() {
$("#selectExcludedGroups").toggleClass('hidden', !this.checked);
});
});

View File

@ -84,3 +84,5 @@ $this->create('settings_admin_mail_test', '/settings/admin/mailtest')
->action('OC\Settings\Admin\Controller', 'sendTestMail');
$this->create('settings_ajax_setsecurity', '/settings/ajax/setsecurity.php')
->actionInclude('settings/ajax/setsecurity.php');
$this->create('settings_ajax_excludegroups', '/settings/ajax/excludegroups.php')
->actionInclude('settings/ajax/excludegroups.php');

View File

@ -240,9 +240,6 @@ if (!$_['internetconnectionworking']) {
</div>
<em><?php p($l->t('Allow users to share items to the public with links')); ?></em>
</td>
</tr>
<tr>
@ -271,7 +268,24 @@ if (!$_['internetconnectionworking']) {
<em><?php p($l->t('Allow users to send mail notification for shared files')); ?></em>
</td>
</tr>
<tr>
<td <?php if ($_['shareAPIEnabled'] === 'no') print_unescaped('class="hidden"');?>>
<input type="checkbox" name="shareapi_exclude_groups" id="shareapiExcludeGroups"
value="1" <?php if ($_['shareExcludeGroups']) print_unescaped('checked="checked"'); ?> />
<label for="shareapiExcludeGroups"><?php p($l->t('Exclude groups from sharing'));?></label><br/>
<div id="selectExcludedGroups" class="<?php ($_['shareExcludeGroups']) ? p('indent') : p('hidden indent'); ?>">
<select
class="groupsselect"
id="excludedGroups" data-placeholder="groups"
title="<?php p($l->t('Groups'))?>" multiple="multiple">
<?php foreach($_["groups"] as $group): ?>
<option value="<?php p($group['gid'])?>" <?php if($group['excluded']) { p('selected="selected"'); }?>><?php p($group['gid']);?></option>
<?php endforeach;?>
</select>
</div>
<em><?php p($l->t('These groups will still be able to receive shares, but not to initiate them.')); ?></em>
</td>
</tr>
</table>
</div>

View File

@ -235,4 +235,59 @@ class Test_Util extends PHPUnit_Framework_TestCase {
array(' .', false),
);
}
/**
* @dataProvider dataProviderForTestIsSharingDisabledForUser
* @param array $groups existing groups
* @param array $membership groups the user belong to
* @param array $excludedGroups groups which should be excluded from sharing
* @param bool $expected expected result
*/
function testIsSharingDisabledForUser($groups, $membership, $excludedGroups, $expected) {
$uid = "user1";
\OC_User::setUserId($uid);
\OC_User::createUser($uid, "passwd");
foreach($groups as $group) {
\OC_Group::createGroup($group);
}
foreach($membership as $group) {
\OC_Group::addToGroup($uid, $group);
}
$appConfig = \OC::$server->getAppConfig();
$appConfig->setValue('core', 'shareapi_exclude_groups_list', implode(',', $excludedGroups));
$appConfig->setValue('core', 'shareapi_exclude_groups', 'yes');
$result = \OCP\Util::isSharingDisabledForUser();
$this->assertSame($expected, $result);
// cleanup
\OC_User::deleteUser($uid);
\OC_User::setUserId('');
foreach($groups as $group) {
\OC_Group::deleteGroup($group);
}
$appConfig->setValue('core', 'shareapi_exclude_groups_list', '');
$appConfig->setValue('core', 'shareapi_exclude_groups', 'no');
}
public function dataProviderForTestIsSharingDisabledForUser() {
return array(
// existing groups, groups the user belong to, groups excluded from sharing, expected result
array(array('g1', 'g2', 'g3'), array(), array('g1'), false),
array(array('g1', 'g2', 'g3'), array(), array(), false),
array(array('g1', 'g2', 'g3'), array('g2'), array('g1'), false),
array(array('g1', 'g2', 'g3'), array('g2'), array(), false),
array(array('g1', 'g2', 'g3'), array('g1', 'g2'), array('g1'), false),
array(array('g1', 'g2', 'g3'), array('g1', 'g2'), array('g1', 'g2'), true),
array(array('g1', 'g2', 'g3'), array('g1', 'g2'), array('g1', 'g2', 'g3'), true),
);
}
}