Skip to content

Commit

Permalink
Merge pull request #947 from nextcloud/enh/noid/context-permissions
Browse files Browse the repository at this point in the history
enh(Contexts): extend permission check
  • Loading branch information
enjeck committed Mar 25, 2024
2 parents 8efc055 + c5536d9 commit 4f9780e
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 28 deletions.
7 changes: 7 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ class Application extends App implements IBootstrap {
public const NAV_ENTRY_MODE_RECIPIENTS = 1;
public const NAV_ENTRY_MODE_ALL = 2;

public const PERMISSION_READ = 1;
public const PERMISSION_CREATE = 2;
public const PERMISSION_UPDATE = 4;
public const PERMISSION_DELETE = 8;
public const PERMISSION_MANAGE = 16;
public const PERMISSION_ALL = 31;

public function __construct() {
parent::__construct(self::APP_ID);
}
Expand Down
34 changes: 34 additions & 0 deletions lib/Db/ContextMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,40 @@ public function findById(int $contextId, ?string $userId = null): Context {
return $this->formatResultRows($r, $userId);
}

/**
* @return Context[]
* @throws Exception
*/
public function findAllContainingNode(int $nodeType, int $nodeId, string $userId): array {
$qb = $this->getFindContextBaseQuery($userId);

$qb->andWhere($qb->expr()->eq('r.node_id', $qb->createNamedParameter($nodeId)))
->andWhere($qb->expr()->eq('r.node_type', $qb->createNamedParameter($nodeType)));

$result = $qb->executeQuery();
$r = $result->fetchAll();

$contextIds = [];
foreach ($r as $row) {
$contextIds[$row['id']] = 1;
}
$contextIds = array_keys($contextIds);
unset($row);

$resultEntities = [];
foreach ($contextIds as $contextId) {
$workArray = [];
foreach ($r as $row) {
if ($row['id'] === $contextId) {
$workArray[] = $row;
}
}
$resultEntities[] = $this->formatResultRows($workArray, $userId);
}

return $resultEntities;
}

protected function applyOwnedOrSharedQuery(IQueryBuilder $qb, string $userId): void {
$sharedToConditions = $qb->expr()->orX();

Expand Down
94 changes: 77 additions & 17 deletions lib/Service/PermissionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,20 +153,7 @@ public function canManageContextById(int $contextId, ?string $userId = null): bo
}

public function canAccessView(View $view, ?string $userId = null): bool {
if($this->basisCheck($view, 'view', $userId)) {
return true;
}

if ($userId) {
try {
$this->getSharedPermissionsIfSharedWithMe($view->getId(), 'view', $userId);
return true;
} catch (NotFoundError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
}

return false;
return $this->canAccessNodeById(Application::NODE_TYPE_VIEW, $view->getId(), $userId);
}

/**
Expand Down Expand Up @@ -458,6 +445,64 @@ public function getSharedPermissionsIfSharedWithMe(int $elementId, string $eleme

// private methods ==========================================================================

/**
* @throws NotFoundError
*/
public function getPermissionIfAvailableThroughContext(int $nodeId, string $nodeType, string $userId): int {
$permissions = 0;
$found = false;
$iNodeType = match ($nodeType) {
'table' => Application::NODE_TYPE_TABLE,
'view' => Application::NODE_TYPE_VIEW,
};
$contexts = $this->contextMapper->findAllContainingNode($iNodeType, $nodeId, $userId);
foreach ($contexts as $context) {
$found = true;
if ($context->getOwnerType() === Application::OWNER_TYPE_USER
&& $context->getOwnerId() === $userId) {
// Making someone owner of a context, makes this person also having manage permissions on the node.
// This is sort of an intended "privilege escalation".
return Application::PERMISSION_ALL;
}
foreach ($context->getNodes() as $nodeRelation) {
$permissions |= $nodeRelation['permissions'];
}
}
if (!$found) {
throw new NotFoundError('Node not found in any context');
}
return $permissions;
}

/**
* @throws NotFoundError
*/
public function getPermissionArrayForNodeFromContexts(int $nodeId, string $nodeType, string $userId) {
$permissions = $this->getPermissionIfAvailableThroughContext($nodeId, $nodeType, $userId);
return [
'read' => (bool)($permissions & Application::PERMISSION_READ),
'create' => (bool)($permissions & Application::PERMISSION_CREATE),
'update' => (bool)($permissions & Application::PERMISSION_UPDATE),
'delete' => (bool)($permissions & Application::PERMISSION_DELETE),
'manage' => (bool)($permissions & Application::PERMISSION_MANAGE),
];
}

private function hasPermission(int $existingPermissions, string $permissionName): bool {
$constantName = 'PERMISSION_' . strtoupper($permissionName);
try {
$permissionBit = constant(Application::class . "::$constantName");
} catch (\Throwable $t) {
$this->logger->error('Unexpected permission string {permission}', [
'app' => Application::APP_ID,
'permission' => $permissionName,
'exception' => $t,
]);
return false;
}
return (bool)($existingPermissions & $permissionBit);
}

/**
* @param mixed $element
* @param 'table'|'view' $nodeType
Expand All @@ -470,13 +515,22 @@ private function checkPermission($element, string $nodeType, string $permission,
return true;
}

if ($userId) {
if (!$userId) {
return false;
}

try {
return $this->getSharedPermissionsIfSharedWithMe($element->getId(), $nodeType, $userId)[$permission];
} catch (NotFoundError $e) {
try {
return $this->getSharedPermissionsIfSharedWithMe($element->getId(), $nodeType, $userId)[$permission];
if ($this->hasPermission($this->getPermissionIfAvailableThroughContext($element->getId(), $nodeType, $userId), $permission)) {
return true;
}
} catch (NotFoundError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
$this->logger->error($e->getMessage(), ['exception' => $e]);
}

return false;
}

Expand All @@ -495,6 +549,12 @@ private function checkPermissionById(int $elementId, string $nodeType, string $p
try {
return $this->getSharedPermissionsIfSharedWithMe($elementId, $nodeType, $userId)[$permission];
} catch (NotFoundError $e) {
try {
if ($this->hasPermission($this->getPermissionIfAvailableThroughContext($elementId, $nodeType, $userId), $permission)) {
return true;
}
} catch (NotFoundError $e) {
}
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
}
Expand Down
6 changes: 6 additions & 0 deletions lib/Service/TableService.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ private function enhanceTable(Table $table, string $userId): void {
$table->setIsShared(true);
$table->setOnSharePermissions($permissions);
} catch (NotFoundError $e) {
try {
$table->setOnSharePermissions($this->permissionsService->getPermissionArrayForNodeFromContexts($table->getId(), 'table', $userId));
$table->setIsShared(true);
} catch (NotFoundError $e) {
}

}
}
if (!$table->getIsShared() || $table->getOnSharePermissions()['manage']) {
Expand Down
23 changes: 12 additions & 11 deletions lib/Service/ViewService.php
Original file line number Diff line number Diff line change
Expand Up @@ -322,24 +322,25 @@ private function enhanceView(View $view, string $userId): void {
if ($userId !== '') {
if ($userId !== $view->getOwnership()) {
try {
$permissions = $this->shareService->getSharedPermissionsIfSharedWithMe($view->getId(), 'view', $userId);
try {
$permissions = $this->shareService->getSharedPermissionsIfSharedWithMe($view->getId(), 'view', $userId);
} catch (NotFoundError) {
$permissions = $this->permissionsService->getPermissionArrayForNodeFromContexts($view->getId(), 'view', $userId);
}
$view->setIsShared(true);
$canManageTable = false;
try {
$manageTableShare = $this->shareService->getSharedPermissionsIfSharedWithMe($view->getTableId(), 'table', $userId);
$canManageTable = $manageTableShare['manage'] ?? false;
try {
$manageTableShare = $this->shareService->getSharedPermissionsIfSharedWithMe($view->getTableId(), 'table', $userId);
} catch (NotFoundError) {
$manageTableShare = $this->permissionsService->getPermissionArrayForNodeFromContexts($view->getTableId(), 'table', $userId);
}
$permissions['manageTable'] = $manageTableShare['manage'] ?? false;
} catch (NotFoundError $e) {
} catch (\Exception $e) {
throw new InternalError($e->getMessage());
}
$view->setOnSharePermissions([
'read' => $permissions['read'] ?? false,
'create' => $permissions['create'] ?? false,
'update' => $permissions['update'] ?? false,
'delete' => $permissions['delete'] ?? false,
'manage' => $permissions['manage'] ?? false,
'manageTable' => $canManageTable
]);
$view->setOnSharePermissions($permissions);
} catch (NotFoundError $e) {
} catch (\Exception $e) {
$this->logger->warning('Exception occurred while setting shared permissions: '.$e->getMessage().' No permissions granted.');
Expand Down

0 comments on commit 4f9780e

Please sign in to comment.