Commit 12338e0e authored by Bjoern Schiessle's avatar Bjoern Schiessle
Browse files

allow admin to disable sharing for specific groups of users

parent 14a953fb
......@@ -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 */
......
......@@ -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') {
......
......@@ -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;
}
......
......@@ -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);
}
......
......@@ -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
......
......@@ -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();
......
......@@ -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';
......
......@@ -80,6 +80,7 @@ $array = array(
'defaultExpireDate' => $defaultExpireDate,
'defaultExpireDateEnforced' => $enforceDefaultExpireDate,
'enforcePasswordForPublicLink' => \OCP\Util::isPublicLinkPasswordRequired(),
'sharingDisabledForUser' => \OCP\Util::isSharingDisabledForUser(),
)
)
),
......
......@@ -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;
}
}
......@@ -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);
}
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
<?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));
......@@ -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;
......
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);
});
});
......@@ -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');
......@@ -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>
......
......@@ -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),
);
}
}
Supports Markdown
0% or .