Skip to content

Commit

Permalink
enh(Contexts): extend permission check
Browse files Browse the repository at this point in the history
- checkPermission(ById) takes inherited permissions via Context into account
- adds query to fetch all contexts containing a certain node
- adds permission constants to Application

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
  • Loading branch information
blizzz committed Mar 21, 2024
1 parent 8efc055 commit c617c85
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 7 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('r.node_id', $qb->createNamedParameter($nodeId))
->andWhere('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
56 changes: 49 additions & 7 deletions lib/Service/PermissionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace OCA\Tables\Service;

use OCA\Files\App;
use OCA\Tables\AppInfo\Application;
use OCA\Tables\Db\ContextMapper;
use OCA\Tables\Db\Share;
Expand Down Expand Up @@ -458,6 +459,42 @@ public function getSharedPermissionsIfSharedWithMe(int $elementId, string $eleme

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

private function getPermissionIfAvailableThroughContext(int $nodeId, string $nodeType, string $userId): int {
$permissions = 0;
$iNodeType = match ($nodeType) {
'table' => Application::NODE_TYPE_TABLE,
'view' => Application::NODE_TYPE_VIEW,
};
$contexts = $this->contextMapper->findAllContainingNode($iNodeType, $nodeId, $userId);
foreach ($contexts as $context) {
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'];
}
}
return $permissions;
}

private function hasPermission(int $existingPermissions, string $permissionName): bool {
$constantName = 'PERMISSION_' . strtoupper($permissionName);
try {
$permissionBit = constant("Application::$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 +507,17 @@ private function checkPermission($element, string $nodeType, string $permission,
return true;
}

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

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

return false;
}

Expand All @@ -493,7 +534,8 @@ private function checkPermissionById(int $elementId, string $nodeType, string $p
}
if ($userId) {
try {
return $this->getSharedPermissionsIfSharedWithMe($elementId, $nodeType, $userId)[$permission];
return $this->getSharedPermissionsIfSharedWithMe($elementId, $nodeType, $userId)[$permission]
|| $this->hasPermission($this->getPermissionIfAvailableThroughContext($elementId, $nodeType, $userId), $permission);
} catch (NotFoundError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
Expand Down

0 comments on commit c617c85

Please sign in to comment.