Skip to content

Commit

Permalink
Merge pull request #966 from nextcloud/share-context
Browse files Browse the repository at this point in the history
feat: Application sharing frontend
  • Loading branch information
juliushaertl committed Apr 24, 2024
2 parents d164cb2 + 98a2a85 commit 9567a5c
Show file tree
Hide file tree
Showing 16 changed files with 552 additions and 89 deletions.
10 changes: 7 additions & 3 deletions lib/Db/ContextMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use OCA\Tables\AppInfo\Application;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Helper\GroupHelper;
use OCA\Tables\Helper\UserHelper;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
Expand All @@ -15,10 +16,8 @@
/** @template-extends QBMapper<Context> */
class ContextMapper extends QBMapper {
protected string $table = 'tables_contexts_context';
private UserHelper $userHelper;

public function __construct(IDBConnection $db, UserHelper $userHelper) {
$this->userHelper = $userHelper;
public function __construct(IDBConnection $db, protected UserHelper $userHelper, protected GroupHelper $groupHelper) {
parent::__construct($db, $this->table, Context::class);
}

Expand Down Expand Up @@ -86,6 +85,11 @@ protected function formatResultRows(array $rows, ?string $userId) {
'share_id' => (int)$item['share_id'],
'receiver' => $item['receiver'],
'receiver_type' => $item['receiver_type'],
'receiver_display_name' => match ($item['receiver_type']) {
'user' => $this->userHelper->getUserDisplayName($item['receiver']),
'group' => $this->groupHelper->getGroupDisplayName($item['receiver']),
default => $item['receiver'],
},
'display_mode_default' => (int)$item['display_mode_default'],
];
if ($userId !== null) {
Expand Down
16 changes: 12 additions & 4 deletions lib/Service/ContextService.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,15 @@ public function update(int $contextId, string $userId, ?string $name, ?string $i
foreach ($nodes as $node) {
$key = sprintf('t%di%d', $node['type'], $node['id']);
if (isset($oldNodeResolvableIdMapper[$key])) {
unset($oldNodeResolvableIdMapper[$key]);
$nodesBeingKept[$key] = $node;
if ($node['permissions'] !== $currentNodes[$oldNodeResolvableIdMapper[$key]]['permissions']) {
$nodeRel = $this->contextNodeRelMapper->findById($currentNodes[$oldNodeResolvableIdMapper[$key]]['id']);
$nodeRel->setPermissions($node['permissions']);
$this->contextNodeRelMapper->update($nodeRel);
$currentNodes[$oldNodeResolvableIdMapper[$key]]['permissions'] = $nodeRel->getPermissions();
$hasUpdatedNodeInformation = true;
}
unset($oldNodeResolvableIdMapper[$key]);
continue;
}
$nodesBeingAdded[$key] = $node;
Expand All @@ -172,7 +179,7 @@ public function update(int $contextId, string $userId, ?string $name, ?string $i
}
unset($nodesBeingKept);

$hasUpdatedNodeInformation = !empty($nodesBeingAdded) || !empty($nodesBeingRemoved);
$hasUpdatedNodeInformation = $hasUpdatedNodeInformation || !empty($nodesBeingAdded) || !empty($nodesBeingRemoved);

foreach ($nodesBeingRemoved as $node) {
/** @var ContextNodeRelation $removedNode */
Expand Down Expand Up @@ -203,10 +210,11 @@ public function update(int $contextId, string $userId, ?string $name, ?string $i
}

$context = $this->contextMapper->update($context);
if ($hasUpdatedNodeInformation && isset($currentNodes) && isset($currentPages)) {
if (isset($currentNodes, $currentPages) && $hasUpdatedNodeInformation) {
$context->setNodes($currentNodes);
$context->setPages($currentPages);
}

return $context;
}

Expand Down Expand Up @@ -422,7 +430,7 @@ protected function insertNodesFromArray(Context $context, array $nodes): void {
if (!$this->permissionsService->canManageNodeById($node['type'], $node['id'], $userId)) {
throw new PermissionError(sprintf('Owner cannot manage node %d (type %d)', $node['id'], $node['type']));
}
$contextNodeRel = $this->addNodeToContext($context, $node['id'], $node['type'], $node['permissions'] ?? 660);
$contextNodeRel = $this->addNodeToContext($context, $node['id'], $node['type'], $node['permissions'] ?? 0);
$addedNodes[] = $contextNodeRel->jsonSerialize();
} catch (Exception $e) {
$this->logger->warning('Could not add node {ntype}/{nid} to context {cid}, skipping.', [
Expand Down
36 changes: 21 additions & 15 deletions lib/Service/PermissionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace OCA\Tables\Service;

use OCA\Tables\AppInfo\Application;
use OCA\Tables\Db\Context;
use OCA\Tables\Db\ContextMapper;
use OCA\Tables\Db\Share;
use OCA\Tables\Db\ShareMapper;
Expand Down Expand Up @@ -149,7 +150,7 @@ public function canManageContextById(int $contextId, ?string $userId = null): bo
return false;
}

return $context->getOwnerId() === $userId;
return $context->getOwnerId() === $userId || $this->canManageContext($context, $userId);
}

/**
Expand Down Expand Up @@ -181,6 +182,8 @@ public function canManageElementById(int $elementId, string $nodeType = 'table',
return $this->canManageTableById($elementId, $userId);
} elseif ($nodeType === 'view') {
return $this->canManageViewById($elementId, $userId);
} elseif ($nodeType === 'context') {
return $this->canManageContextById($elementId, $userId);
} else {
throw new InternalError('Cannot read permission for node type '.$nodeType);
}
Expand All @@ -199,6 +202,10 @@ public function canManageTable(Table $table, ?string $userId = null): bool {
return $this->checkPermission($table, 'table', 'manage', $userId);
}

public function canManageContext(Context $context, ?string $userId = null): bool {
return $this->checkPermission($context, 'context', 'manage', $userId);
}

public function canManageTableById(int $tableId, ?string $userId = null): bool {
try {
$table = $this->tableMapper->find($tableId);
Expand Down Expand Up @@ -516,13 +523,13 @@ private function hasPermission(int $existingPermissions, string $permissionName)
}

/**
* @param mixed $element
* @param 'table'|'view' $nodeType
* @param Table|View|Context $element
* @param 'table'|'view'|'context' $nodeType
* @param string $permission
* @param string|null $userId
* @return bool
*/
private function checkPermission($element, string $nodeType, string $permission, ?string $userId = null): bool {
private function checkPermission(Table|View|Context $element, string $nodeType, string $permission, ?string $userId = null): bool {
if($this->basisCheck($element, $nodeType, $userId)) {
return true;
}
Expand All @@ -535,7 +542,9 @@ private function checkPermission($element, string $nodeType, string $permission,
return $this->getSharedPermissionsIfSharedWithMe($element->getId(), $nodeType, $userId)[$permission];
} catch (NotFoundError $e) {
try {
if ($this->hasPermission($this->getPermissionIfAvailableThroughContext($element->getId(), $nodeType, $userId), $permission)) {
if ($nodeType !== 'context'
&& $this->hasPermission($this->getPermissionIfAvailableThroughContext($element->getId(), $nodeType, $userId), $permission)
) {
return true;
}
} catch (NotFoundError $e) {
Expand Down Expand Up @@ -573,13 +582,7 @@ private function checkPermissionById(int $elementId, string $nodeType, string $p
return false;
}

/**
* @param Table|View $element
* @param string $nodeType
* @param string|null $userId
* @return bool
*/
private function basisCheck($element, string $nodeType, ?string &$userId): bool {
private function basisCheck(Table|View|Context $element, string $nodeType, ?string &$userId): bool {
try {
$userId = $this->preCheckUserId($userId);
} catch (InternalError $e) {
Expand All @@ -592,7 +595,7 @@ private function basisCheck($element, string $nodeType, ?string &$userId): bool
return true;
}

if ($this->userIsElementOwner($element, $userId)) {
if ($this->userIsElementOwner($element, $userId, $nodeType)) {
return true;
}
try {
Expand Down Expand Up @@ -633,11 +636,14 @@ private function basisCheckById(int $elementId, string $nodeType, ?string &$user
}

/**
* @param View|Table $element
* @param View|Table|Context $element
* @param string|null $userId
* @return bool
*/
private function userIsElementOwner($element, string $userId = null): bool {
private function userIsElementOwner($element, string $userId = null, ?string $nodeType = null): bool {
if ($nodeType === 'context') {
return $element->getOwnerId() === $userId;
}
return $element->getOwnership() === $userId;
}

Expand Down
39 changes: 26 additions & 13 deletions lib/Service/TableService.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class TableService extends SuperService {


protected IL10N $l;
private ContextService $contextService;

protected IEventDispatcher $eventDispatcher;

Expand All @@ -59,8 +60,9 @@ public function __construct(
ShareService $shareService,
UserHelper $userHelper,
FavoritesService $favoritesService,
IEventDispatcher $eventDispatcher,
ContextService $contextService,
IL10N $l,
IEventDispatcher $eventDispatcher
) {
parent::__construct($logger, $userId, $permissionsService);
$this->mapper = $mapper;
Expand All @@ -73,6 +75,7 @@ public function __construct(
$this->favoritesService = $favoritesService;
$this->l = $l;
$this->eventDispatcher = $eventDispatcher;
$this->contextService = $contextService;
}

/**
Expand All @@ -91,9 +94,13 @@ public function __construct(
public function findAll(?string $userId = null, bool $skipTableEnhancement = false, bool $skipSharedTables = false, bool $createTutorial = true): array {
/** @var string $userId */
$userId = $this->permissionsService->preCheckUserId($userId); // $userId can be set or ''
$allTables = [];

try {
$allTables = $this->mapper->findAll($userId); // get own tables
$ownedTables = $this->mapper->findAll($userId); // get own tables
foreach ($ownedTables as $ownedTable) {
$allTables[$ownedTable->getId()] = $ownedTable;
}
} catch (OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError($e->getMessage());
Expand All @@ -102,7 +109,8 @@ public function findAll(?string $userId = null, bool $skipTableEnhancement = fal
// if there are no own tables found, create the tutorial table
if (count($allTables) === 0 && $createTutorial) {
try {
$allTables = [$this->create($this->l->t('Tutorial'), 'tutorial', '🚀')];
$tutorialTable = $this->create($this->l->t('Tutorial'), 'tutorial', '🚀');
$allTables[$tutorialTable->getId()] = $tutorialTable;
} catch (InternalError|PermissionError|DoesNotExistException|MultipleObjectsReturnedException|OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
Expand All @@ -114,16 +122,22 @@ public function findAll(?string $userId = null, bool $skipTableEnhancement = fal

// clean duplicates
foreach ($sharedTables as $sharedTable) {
$found = false;
foreach ($allTables as $table) {
if ($sharedTable->getId() === $table->getId()) {
$found = true;
break;
}
if (!isset($allTables[$sharedTable->getId()])) {
$allTables[$sharedTable->getId()] = $sharedTable;
}
if (!$found) {
$allTables[] = $sharedTable;
}
}

$contexts = $this->contextService->findAll($userId);
foreach ($contexts as $context) {
$nodes = $context->getNodes();
foreach ($nodes as $node) {
if ($node['node_type'] !== Application::NODE_TYPE_TABLE
|| isset($allTables[$node['node_id']])
) {
continue;
}
$allTables[$node['node_id']] = $this->find($node['node_id'], $skipTableEnhancement, $userId);
}
}

Expand All @@ -144,8 +158,7 @@ public function findAll(?string $userId = null, bool $skipTableEnhancement = fal
}
}


return $allTables;
return array_values($allTables);
}

/**
Expand Down
31 changes: 27 additions & 4 deletions lib/Service/ViewService.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ViewService extends SuperService {
protected FavoritesService $favoritesService;

protected IL10N $l;
private ContextService $contextService;

protected IEventDispatcher $eventDispatcher;

Expand All @@ -52,8 +53,9 @@ public function __construct(
RowService $rowService,
UserHelper $userHelper,
FavoritesService $favoritesService,
IL10N $l,
IEventDispatcher $eventDispatcher
IEventDispatcher $eventDispatcher,
ContextService $contextService,
IL10N $l
) {
parent::__construct($logger, $userId, $permissionsService);
$this->l = $l;
Expand All @@ -63,6 +65,7 @@ public function __construct(
$this->userHelper = $userHelper;
$this->favoritesService = $favoritesService;
$this->eventDispatcher = $eventDispatcher;
$this->contextService = $contextService;
}


Expand Down Expand Up @@ -148,11 +151,31 @@ public function findSharedViewsWithMe(?string $userId = null): array {
if ($userId === '') {
return [];
}

$allViews = [];

$sharedViews = $this->shareService->findViewsSharedWithMe($userId);
foreach ($sharedViews as $view) {
foreach ($sharedViews as $sharedView) {
$allViews[$sharedView->getId()] = $sharedView;
}

$contexts = $this->contextService->findAll($userId);
foreach ($contexts as $context) {
$nodes = $context->getNodes();
foreach ($nodes as $node) {
if ($node['node_type'] !== Application::NODE_TYPE_VIEW
|| isset($allViews[$node['node_id']])
) {
continue;
}
$allViews[$node['node_id']] = $this->find($node['node_id'], false, $userId);
}
}

foreach ($allViews as $view) {
$this->enhanceView($view, $userId);
}
return $sharedViews;
return array_values($allViews);
}


Expand Down
12 changes: 7 additions & 5 deletions src/modules/modals/CreateContext.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
<div class="col-4">
{{ t('tables', 'Resources') }}
</div>
<NcContextResource :resources.sync="resources" />
<NcContextResource :resources.sync="resources" :receivers.sync="receivers" />
</div>
<div class="row space-R">
<div class="row space-R row space-T">
<div class="fix-col-4 end">
<NcButton type="primary" :aria-label="t('tables', 'Create application')" @click="submit">
{{ t('tables', 'Create application') }}
Expand All @@ -57,6 +57,7 @@ import '@nextcloud/dialogs/dist/index.css'
import NcContextResource from '../../shared/components/ncContextResource/NcContextResource.vue'
import NcIconPicker from '../../shared/components/ncIconPicker/NcIconPicker.vue'
import svgHelper from '../../shared/components/ncIconPicker/mixins/svgHelper.js'
import permissionBitmask from '../../shared/components/ncContextResource/mixins/permissionBitmask.js'
export default {
name: 'CreateContext',
Expand All @@ -67,7 +68,7 @@ export default {
NcIconSvgWrapper,
NcContextResource,
},
mixins: [svgHelper],
mixins: [svgHelper, permissionBitmask],
props: {
showModal: {
type: Boolean,
Expand All @@ -86,6 +87,7 @@ export default {
errorTitle: false,
description: '',
resources: [],
receivers: [],
}
},
watch: {
Expand Down Expand Up @@ -130,7 +132,7 @@ export default {
return {
id: parseInt(resource.id),
type: parseInt(resource.nodeType),
permissions: 660,
permissions: this.getPermissionBitmaskFromBools(true /* ensure read permission is always true */, resource.permissionCreate, resource.permissionUpdate, resource.permissionDelete),
}
})
const data = {
Expand All @@ -139,7 +141,7 @@ export default {
description: this.description,
nodes: dataResources,
}
const res = await this.$store.dispatch('insertNewContext', { data })
const res = await this.$store.dispatch('insertNewContext', { data, previousReceivers: [], receivers: this.receivers })
if (res) {
return res.id
} else {
Expand Down
Loading

0 comments on commit 9567a5c

Please sign in to comment.