Add setting for managing ACL rules as regular user

Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
Julius Härtl 2019-07-15 12:17:53 +02:00
parent 24a1d023ae
commit e4a5747cda
No known key found for this signature in database
GPG key ID: 4C614C6ED2CDE6DF
13 changed files with 173 additions and 30 deletions

View file

@ -41,6 +41,11 @@ return ['routes' => [
'url' => '/folders/{id}/groups/{group}',
'verb' => 'POST'
],
[
'name' => 'Folder#setManageACL',
'url' => '/folders/{id}/groups/{group}/manageACL',
'verb' => 'POST'
],
[
'name' => 'Folder#setQuota',
'url' => '/folders/{id}/quota',

View file

@ -1,17 +1,23 @@
import {OCSResult} from "NC";
import Thenable = JQuery.Thenable;
import {FolderGroupsProps} from "./FolderGroups";
export interface Group {
id: string;
displayname: string;
}
export interface GroupProps {
permissions: number;
manage_acl: boolean;
}
export interface Folder {
id: number;
mount_point: string;
quota: number;
size: number;
groups: { [group: string]: number };
groups: { [group: string]: GroupProps };
acl: boolean;
}
@ -73,6 +79,12 @@ export class Api {
});
}
setManageACL(folderId: number, group: string, manageACL: boolean): Thenable<void> {
return $.post(this.getUrl(`folders/${folderId}/groups/${group}/manageACL`), {
manageAcl: manageACL ? 1 : 0
});
}
setQuota(folderId: number, quota: number): Thenable<void> {
return $.post(this.getUrl(`folders/${folderId}/quota`), {
quota

View file

@ -98,7 +98,7 @@ export class App extends Component<{}, AppState> implements OC.Plugin<OC.Search.
addGroup(folder: Folder, group: string) {
const folders = this.state.folders;
folder.groups[group] = OC.PERMISSION_ALL;
folder.groups[group] = {permissions: OC.PERMISSION_ALL, manage_acl: false};
this.setState({folders});
this.api.addGroup(folder.id, group);
}
@ -112,11 +112,18 @@ export class App extends Component<{}, AppState> implements OC.Plugin<OC.Search.
setPermissions(folder: Folder, group: string, newPermissions: number) {
const folders = this.state.folders;
folder.groups[group] = newPermissions;
folder.groups[group].permissions = newPermissions;
this.setState({folders});
this.api.setPermissions(folder.id, group, newPermissions);
}
setManageACL(folder: Folder, group: string, manageACL: boolean) {
const folders = this.state.folders;
folder.groups[group].manage_acl = manageACL;
this.setState({folders});
this.api.setManageACL(folder.id, group, manageACL);
}
setQuota(folder: Folder, quota: number) {
const folders = this.state.folders;
folder.quota = quota;
@ -218,6 +225,7 @@ export class App extends Component<{}, AppState> implements OC.Plugin<OC.Search.
onAddGroup={this.addGroup.bind(this, folder)}
removeGroup={this.removeGroup.bind(this, folder)}
onSetPermissions={this.setPermissions.bind(this, folder)}
onSetManageACL={this.setManageACL.bind(this, folder)}
/>
</td>
<td className="quota">

View file

@ -1,7 +1,7 @@
import * as React from 'react';
import './FolderGroups.css';
import {SyntheticEvent} from "react";
import {Group} from "./Api";
import {Folder, Group, GroupProps} from "./Api";
import Select from 'react-select'
function hasPermissions(value: number, check: number): boolean {
@ -9,24 +9,29 @@ function hasPermissions(value: number, check: number): boolean {
}
export interface FolderGroupsProps {
groups: { [group: string]: number },
groups: { [group: string]: GroupProps },
allGroups?: Group[],
onAddGroup: (name: string) => void;
removeGroup: (name: string) => void;
edit: boolean;
showEdit: (event: SyntheticEvent<any>) => void;
onSetPermissions: (name: string, permissions: number) => void;
onSetManageACL: (name: string, manageACL: boolean) => void;
}
export function FolderGroups({groups, allGroups = [], onAddGroup, removeGroup, edit, showEdit, onSetPermissions}: FolderGroupsProps) {
export function FolderGroups({groups, allGroups = [], onAddGroup, removeGroup, edit, showEdit, onSetPermissions, onSetManageACL}: FolderGroupsProps) {
if (edit) {
const setPermissions = (change: number, groupId: string): void => {
const newPermissions = groups[groupId] ^ change;
const newPermissions = groups[groupId].permissions ^ change;
onSetPermissions(groupId, newPermissions);
};
const setManageACL = (change: boolean, groupId: string): void => {
onSetManageACL(groupId, change);
};
const rows = Object.keys(groups).map(groupId => {
const permissions = groups[groupId];
const group = groups[groupId];
return <tr key={groupId}>
<td>
{(
@ -41,17 +46,22 @@ export function FolderGroups({groups, allGroups = [], onAddGroup, removeGroup, e
<td className="permissions">
<input type="checkbox"
onChange={setPermissions.bind(null, OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE, groupId)}
checked={hasPermissions(permissions, (OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE))}/>
checked={hasPermissions(group.permissions, (OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE))}/>
</td>
<td className="permissions">
<input type="checkbox"
onChange={setPermissions.bind(null, OC.PERMISSION_SHARE, groupId)}
checked={hasPermissions(permissions, OC.PERMISSION_SHARE)}/>
checked={hasPermissions(group.permissions, OC.PERMISSION_SHARE)}/>
</td>
<td className="permissions">
<input type="checkbox"
onChange={setPermissions.bind(null, OC.PERMISSION_DELETE, groupId)}
checked={hasPermissions(permissions, (OC.PERMISSION_DELETE))}/>
checked={hasPermissions(group.permissions, (OC.PERMISSION_DELETE))}/>
</td>
<td className="permissions">
<input type="checkbox"
onChange={setManageACL.bind(null, !group.manage_acl, groupId)}
checked={group.manage_acl}/>
</td>
<td>
<a onClick={removeGroup.bind(this, groupId)}>
@ -69,6 +79,7 @@ export function FolderGroups({groups, allGroups = [], onAddGroup, removeGroup, e
<th>Write</th>
<th>Share</th>
<th>Delete</th>
<th>Manage ACL</th>
<th/>
</tr>
</thead>
@ -110,7 +121,9 @@ interface GroupSelectProps {
function GroupSelect({allGroups, onChange}: GroupSelectProps) {
if (allGroups.length === 0) {
return <div/>;
return <div>
<p>No other groups available</p>
</div>;
}
const options = allGroups.map(group => {
return {

View file

@ -59,6 +59,7 @@ class Group extends Base {
->addArgument('folder_id', InputArgument::REQUIRED, 'Id of the folder to configure')
->addArgument('group', InputArgument::REQUIRED, 'The group to configure')
->addArgument('permissions', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The permissions to set for the group, leave empty for read only')
->addOption('manage-acl', 'a', InputOption::VALUE_REQUIRED, 'The group has permission to manage advanced permissions')
->addOption('delete', 'd', InputOption::VALUE_NONE, 'Remove access for the group');
parent::configure();
@ -81,6 +82,9 @@ class Group extends Base {
$this->folderManager->addApplicableGroup($folderId, $groupString);
}
$this->folderManager->setGroupPermissions($folderId, $groupString, $permissions);
if ($input->hasOption('manage-acl')) {
$this->folderManager->setManageACL($folderId, $groupString, (bool)$input->getOption('manage-acl'));
}
return 0;
} else {
$output->writeln('<error>Unable to parse permissions input: ' . implode(' ', $permissionsString) . '</error>');

View file

@ -78,8 +78,9 @@ class ListCommand extends Base {
$table->setRows(array_map(function ($folder) {
$folder['size'] = \OCP\Util::humanFileSize($folder['size']);
$folder['quota'] = ($folder['quota'] > 0) ? \OCP\Util::humanFileSize($folder['quota']) : 'Unlimited';
$groupStrings = array_map(function (string $groupId, int $permissions) {
return $groupId . ': ' . $this->permissionsToString($permissions);
$groupStrings = array_map(function (string $groupId, array $applicable) {
$manageAclString = $applicable['manage_acl'] ? ', acl': '';
return $groupId . ': ' . $this->permissionsToString((int)$applicable['permissions']) . $manageAclString;
}, array_keys($folder['groups']), array_values($folder['groups']));
$folder['groups'] = implode("\n", $groupStrings);
$folder['acl'] = $folder['acl'] ? 'Enabled' : 'Disabled';

View file

@ -37,18 +37,22 @@ class FolderController extends OCSController {
private $mountProvider;
/** @var IRootFolder */
private $rootFolder;
/** @var string */
private $userId;
public function __construct(
$AppName,
IRequest $request,
FolderManager $manager,
MountProvider $mountProvider,
IRootFolder $rootFolder
IRootFolder $rootFolder,
$userId
) {
parent::__construct($AppName, $request);
$this->manager = $manager;
$this->mountProvider = $mountProvider;
$this->rootFolder = $rootFolder;
$this->userId = $userId;
$this->registerResponder('xml', function ($data) {
return $this->buildOCSResponseXML('xml', $data);
@ -133,6 +137,16 @@ class FolderController extends OCSController {
$this->manager->setGroupPermissions($id, $group, $permissions);
return new DataResponse(true);
}
/**
* @param int $id
* @param string $group
* @param bool $manageAcl
* @return DataResponse
*/
public function setManageACL($id, $group, $manageAcl) {
$this->manager->setManageACL($id, $group, $manageAcl);
return new DataResponse(true);
}
/**
* @param int $id
@ -195,14 +209,20 @@ class FolderController extends OCSController {
}
/**
* @NoAdminRequired
* @param $id
* @param $fileId
* @param string $search
* @return DataResponse
*/
public function aclMappingSearch($id, $fileId, $search = ''): DataResponse {
$groups = $this->manager->searchGroups($id, $search);
$users = $this->manager->searchUsers($id, $search);
$users = [];
$groups = [];
if ($this->manager->canManageACL($id, $this->userId) === true) {
$groups = $this->manager->searchGroups($id, $search);
$users = $this->manager->searchUsers($id, $search);
}
return new DataResponse([
'users' => $users,
'groups' => $groups,

View file

@ -39,6 +39,7 @@ use Sabre\Xml\Reader;
class ACLPlugin extends ServerPlugin {
const ACL_ENABLED = '{http://nextcloud.org/ns}acl-enabled';
const ACL_CAN_MANAGE = '{http://nextcloud.org/ns}acl-can-manage';
const ACL_LIST = '{http://nextcloud.org/ns}acl-list';
const INHERITED_ACL_LIST = '{http://nextcloud.org/ns}inherited-acl-list';
const GROUP_FOLDER_ID = '{http://nextcloud.org/ns}group-folder-id';
@ -66,8 +67,9 @@ class ACLPlugin extends ServerPlugin {
$this->folderManager = $folderManager;
}
private function isAdmin() {
return $this->groupManager->isAdmin($this->user->getUID());
private function isAdmin($path): bool {
$folderId = $this->folderManager->getFolderByPath($path);
return $this->folderManager->canManageACL($folderId, $this->user->getUID());
}
public function initialize(Server $server) {
@ -113,7 +115,7 @@ class ACLPlugin extends ServerPlugin {
$propFind->handle(self::ACL_LIST, function () use ($fileInfo, $mount) {
$path = trim($mount->getSourcePath() . '/' . $fileInfo->getInternalPath(), '/');
if ($this->isAdmin()) {
if ($this->isAdmin($fileInfo->getPath())) {
$rules = $this->ruleManager->getAllRulesForPaths($mount->getNumericStorageId(), [$path]);
} else {
$rules = $this->ruleManager->getRulesForFilesByPath($this->user, $mount->getNumericStorageId(), [$path]);
@ -126,7 +128,7 @@ class ACLPlugin extends ServerPlugin {
$parentPaths = array_map(function (string $internalPath) use ($mount) {
return trim($mount->getSourcePath() . '/' . $internalPath, '/');
}, $parentInternalPaths);
if ($this->isAdmin()) {
if ($this->isAdmin($fileInfo->getPath())) {
$rulesByPath = $this->ruleManager->getAllRulesForPaths($mount->getNumericStorageId(), $parentPaths);
} else {
$rulesByPath = $this->ruleManager->getRulesForFilesByPath($this->user, $mount->getNumericStorageId(), $parentPaths);
@ -168,10 +170,19 @@ class ACLPlugin extends ServerPlugin {
$folder = $this->folderManager->getFolder($folderId, -1);
return $folder['acl'];
});
$propFind->handle(self::ACL_CAN_MANAGE, function () use ($fileInfo) {
return $this->isAdmin($fileInfo->getPath());
});
}
function propPatch($path, PropPatch $propPatch) {
if (!$this->isAdmin()) {
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof Node) {
return false;
}
$fileInfo = $node->getFileInfo();
if (!$this->isAdmin($fileInfo->getPath())) {
return;
}

View file

@ -101,7 +101,7 @@ class FolderManager {
}
public function getAllFoldersWithSize($rootStorageId) {
$applicableMap = $this->getAllApplicable();
$applicableMap = $this->getAllApplicable(false);
$query = $this->connection->getQueryBuilder();
@ -127,8 +127,8 @@ class FolderManager {
return $folderMap;
}
public function getFolder($id, $rootStorageId) {
$applicableMap = $this->getAllApplicable();
public function getFolder($id, $rootStorageId, $onlyPermissions = true) {
$applicableMap = $this->getAllApplicable($onlyPermissions);
$query = $this->connection->getQueryBuilder();
@ -156,10 +156,10 @@ class FolderManager {
return $mountpoint->getFolderId();
}
private function getAllApplicable() {
private function getAllApplicable(bool $permissionOnly = true) {
$query = $this->connection->getQueryBuilder();
$query->select('folder_id', 'group_id', 'permissions')
$query->select('folder_id', 'group_id', 'permissions', 'manage_acl')
->from('group_folders_groups');
$rows = $query->execute()->fetchAll();
@ -170,7 +170,12 @@ class FolderManager {
if (!isset($applicableMap[$id])) {
$applicableMap[$id] = [];
}
$applicableMap[$id][$row['group_id']] = (int)$row['permissions'];
if ($permissionOnly) {
$applicableMap[$id][$row['group_id']] = (int)$row['permissions'];
} else {
$applicableMap[$id][$row['group_id']]['permissions'] = (int)$row['permissions'];
$applicableMap[$id][$row['group_id']]['manage_acl'] = (bool)$row['manage_acl'];
}
}
return $applicableMap;
@ -187,6 +192,18 @@ class FolderManager {
}, array_keys($groups));
}
public function canManageACL($folderId, $userId): bool {
$folder = $this->getFolder($folderId, -1, false);
$groups = $folder['groups'];
foreach ($groups as $group => $groupDetails) {
$canManageACL = $groupDetails['manage_acl'] ?? false;
if ((bool)$canManageACL === true && $this->groupManager->isInGroup($userId, $group)) {
return true;
}
}
return false;
}
public function searchGroups($id, $search = ''): array {
$groups = $this->getGroups($id);
if ($search === '') {
@ -304,6 +321,17 @@ class FolderManager {
$query->execute();
}
public function setManageACL($folderId, $groupId, $manageAcl) {
$query = $this->connection->getQueryBuilder();
$query->update('group_folders_groups')
->set('manage_acl', $query->createNamedParameter($manageAcl, IQueryBuilder::PARAM_BOOL))
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('group_id', $query->createNamedParameter($groupId)));
$query->execute();
}
public function removeFolder($folderId) {
$query = $this->connection->getQueryBuilder();

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace OCA\GroupFolders\Migration;
use Closure;
use Doctrine\DBAL\Types\Type;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version401000Date20190715092137 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('group_folders_groups');
if (!$table->hasColumn('manage_acl')) {
$table->addColumn('manage_acl', Type::BOOLEAN, [
'notnull' => true,
'default' => false
]);
}
return $schema;
}
}

View file

@ -135,6 +135,11 @@ client.addFileInfoParser(function(response) {
data.aclEnabled = !!aclEnabled;
}
var aclCanManage = props[ACL_PROPERTIES.PROPERTY_ACL_CAN_MANAGE];
if (typeof aclCanManage !== 'undefined') {
data.aclCanManage = !!aclCanManage;
}
var acls = props[ACL_PROPERTIES.PROPERTY_ACL_LIST];
var inheritedAcls = props[ACL_PROPERTIES.PROPERTY_INHERITED_ACL_LIST];
@ -158,7 +163,7 @@ class AclDavService {
propFind(model) {
return client.getFileInfo(model.path + '/' + model.name, {
properties: [ACL_PROPERTIES.PROPERTY_ACL_LIST, ACL_PROPERTIES.PROPERTY_INHERITED_ACL_LIST, ACL_PROPERTIES.GROUP_FOLDER_ID, ACL_PROPERTIES.PROPERTY_ACL_ENABLED]
properties: [ACL_PROPERTIES.PROPERTY_ACL_LIST, ACL_PROPERTIES.PROPERTY_INHERITED_ACL_LIST, ACL_PROPERTIES.GROUP_FOLDER_ID, ACL_PROPERTIES.PROPERTY_ACL_ENABLED, ACL_PROPERTIES.PROPERTY_ACL_CAN_MANAGE]
} ).then((status, fileInfo) => {
if (fileInfo) {
let acls = []
@ -176,6 +181,7 @@ class AclDavService {
return {
acls,
aclEnabled: fileInfo.aclEnabled,
aclCanManage: fileInfo.aclCanManage,
groupFolderId: fileInfo.groupFolderId
};
}

View file

@ -140,6 +140,7 @@
this.list = data.acls;
}
this.aclEnabled = data.aclEnabled;
this.aclCanManage = data.aclCanManage;
this.groupFolderId = data.groupFolderId;
this.loading = false;
this.searchMappings('')
@ -148,6 +149,7 @@
data () {
return {
aclEnabled: false,
aclCanManage: false,
showAclCreate: false,
groupFolderId: null,
loading: false,
@ -159,7 +161,7 @@
},
computed: {
isAdmin () {
return OC.isUserAdmin()
return this.aclCanManage
},
isInherited () {
return (permission, permissions, mask) => {

View file

@ -28,6 +28,7 @@ const ACL_PROPERTIES = {
PROPERTY_ACL_MASK: '{' + OC.Files.Client.NS_NEXTCLOUD + '}acl-mask',
PROPERTY_ACL_PERMISSIONS: '{' + OC.Files.Client.NS_NEXTCLOUD + '}acl-permissions',
PROPERTY_ACL_ENABLED: '{' + OC.Files.Client.NS_NEXTCLOUD + '}acl-enabled',
PROPERTY_ACL_CAN_MANAGE: '{' + OC.Files.Client.NS_NEXTCLOUD + '}acl-can-manage',
PROPERTY_INHERITED_ACL_LIST: '{' + OC.Files.Client.NS_NEXTCLOUD + '}inherited-acl-list',
GROUP_FOLDER_ID: '{' + OC.Files.Client.NS_NEXTCLOUD + '}group-folder-id'
};