Compare commits
57 Commits
backport/4
...
stable24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93307bb247 | ||
|
|
45f2eedafe | ||
|
|
6fb0f6209c | ||
|
|
043327dc09 | ||
|
|
05aafd9ddb | ||
|
|
5f789962b4 | ||
|
|
ff2c11c796 | ||
|
|
3dee8587b7 | ||
|
|
86751c4b89 | ||
|
|
f70b31c449 | ||
|
|
52c5020ba4 | ||
|
|
1f71d1ab78 | ||
|
|
9119017ce8 | ||
|
|
0c0bb40515 | ||
|
|
0954d4d8a0 | ||
|
|
272da5406a | ||
|
|
aef06d833d | ||
|
|
71948d670e | ||
|
|
22a3efe445 | ||
|
|
6aca034222 | ||
|
|
b37ba6a409 | ||
|
|
ffb99b8010 | ||
|
|
7e33f38b4c | ||
|
|
5391689eb0 | ||
|
|
e289e05c98 | ||
|
|
efb0bef5e1 | ||
|
|
1825ae4711 | ||
|
|
c4bdffbce5 | ||
|
|
7eb57b0ed1 | ||
|
|
3516e7a9c2 | ||
|
|
5a5ee3cd4f | ||
|
|
574aa325a0 | ||
|
|
933e0b097e | ||
|
|
2a6023cf86 | ||
|
|
b1ee1ae156 | ||
|
|
892727fed0 | ||
|
|
cced84af3d | ||
|
|
f33477fe1a | ||
|
|
bafc3e5221 | ||
|
|
67ecf24d86 | ||
|
|
10bda7253e | ||
|
|
1713343dd4 | ||
|
|
c2cf3feb25 | ||
|
|
56792e048a | ||
|
|
dae3144c25 | ||
|
|
31b73fe6bb | ||
|
|
ef686bef34 | ||
|
|
50609944bc | ||
|
|
21727cfb3a | ||
|
|
a93b874b89 | ||
|
|
c573c33926 | ||
|
|
9ff9858e9e | ||
|
|
aa3c80e8d0 | ||
|
|
56c4d45712 | ||
|
|
4f67d2fe60 | ||
|
|
875ac0d6e9 | ||
|
|
741df20b49 |
39
.github/workflows/appbuild.yml
vendored
39
.github/workflows/appbuild.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Package build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
- name: install dependencies
|
||||
run: |
|
||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.12.2/krankerl_0.12.2_amd64.deb
|
||||
sudo dpkg -i krankerl_0.12.2_amd64.deb
|
||||
- name: package
|
||||
run: |
|
||||
uname -a
|
||||
RUST_BACKTRACE=1 krankerl --version
|
||||
RUST_BACKTRACE=1 krankerl package
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Deck app tarball
|
||||
path: build/artifacts/deck.tar.gz
|
||||
19
.github/workflows/fixup.yml
vendored
19
.github/workflows/fixup.yml
vendored
@@ -3,18 +3,31 @@
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Pull request checks
|
||||
name: Block fixup and squash commits
|
||||
|
||||
on: pull_request
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: fixup-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
commit-message-check:
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
name: Block fixup and squash commits
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Run check
|
||||
uses: xt0rted/block-autosquash-commits-action@v2
|
||||
uses: skjnldsv/block-fixup-merge-action@42d26e1b536ce61e5cf467d65fb76caf4aa85acf # v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
18
.github/workflows/integration.yml
vendored
18
.github/workflows/integration.yml
vendored
@@ -61,17 +61,25 @@ jobs:
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Checkout activity
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
with:
|
||||
repository: nextcloud/activity
|
||||
ref: ${{ matrix.server-versions }}
|
||||
path: apps/activity
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@2.25.4
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql,
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, apcu
|
||||
ini-values:
|
||||
apc.enable_cli=on
|
||||
coverage: none
|
||||
|
||||
- name: Set up PHPUnit
|
||||
- name: Set up dependencies
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: composer i
|
||||
run: composer i --no-dev
|
||||
|
||||
- name: Set up Nextcloud
|
||||
run: |
|
||||
|
||||
5
.github/workflows/phpunit.yml
vendored
5
.github/workflows/phpunit.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['7.4', '8.0', '8.1']
|
||||
php-versions: ['7.4', '8.0']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable24']
|
||||
|
||||
@@ -62,11 +62,12 @@ jobs:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
uses: shivammathur/setup-php@2.24.0
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
extensions: zip, gd, mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql
|
||||
ini-file: development
|
||||
coverage: none
|
||||
|
||||
- name: Set up PHPUnit
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.7.5
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix(occ): set user id for permission sevice from board service [#5074](https://github.com/nextcloud/deck/pull/5074)
|
||||
- Fix small issues around delete/undo @juliushaertl [#5446](https://github.com/nextcloud/deck/pull/5446)
|
||||
- Fix deleted card/board issues @juliushaertl [#5449](https://github.com/nextcloud/deck/pull/5449)
|
||||
|
||||
## 1.7.4
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>1.7.4</version>
|
||||
<version>1.7.5</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<namespace>Deck</namespace>
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
"@test:integration"
|
||||
],
|
||||
"test:unit": "phpunit -c tests/phpunit.xml",
|
||||
"test:integration": "phpunit -c tests/phpunit.integration.xml && cd tests/integration && ./run.sh"
|
||||
"test:integration": "phpunit -c tests/phpunit.integration.xml",
|
||||
"test:api": "cd tests/integration && ./run.sh"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
|
||||
@@ -38,6 +38,7 @@ use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\Activity\IEvent;
|
||||
use OCP\Activity\IManager;
|
||||
@@ -549,4 +550,24 @@ class ActivityManager {
|
||||
'board' => $board
|
||||
];
|
||||
}
|
||||
|
||||
public function canSeeCardActivity(int $cardId): bool {
|
||||
try {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
return $card->getDeletedAt() === 0;
|
||||
} catch (NoPermissionException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function canSeeBoardActivity(int $boardId): bool {
|
||||
try {
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||
$board = $this->boardMapper->find($boardId);
|
||||
return $board->getDeletedAt() === 0;
|
||||
} catch (NoPermissionException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,9 @@ class DeckProvider implements IProvider {
|
||||
$event->setAuthor($author);
|
||||
}
|
||||
if ($event->getObjectType() === ActivityManager::DECK_OBJECT_BOARD) {
|
||||
if (!$this->activityManager->canSeeBoardActivity($event->getObjectId())) {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
if (isset($subjectParams['board']) && $event->getObjectName() === '') {
|
||||
$event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['board']['title']);
|
||||
}
|
||||
@@ -125,6 +128,9 @@ class DeckProvider implements IProvider {
|
||||
}
|
||||
|
||||
if (isset($subjectParams['card']) && $event->getObjectType() === ActivityManager::DECK_OBJECT_CARD) {
|
||||
if (!$this->activityManager->canSeeCardActivity($event->getObjectId())) {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
if ($event->getObjectName() === '') {
|
||||
$event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['card']['title']);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ class ResourceProvider implements IProvider {
|
||||
|
||||
private function getBoard(IResource $resource) {
|
||||
try {
|
||||
return $this->boardMapper->find($resource->getId(), false, true);
|
||||
return $this->boardMapper->find((int)$resource->getId(), false, true);
|
||||
} catch (DoesNotExistException $e) {
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
return null;
|
||||
|
||||
@@ -79,12 +79,14 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
public function find($id, $withLabels = false, $withAcl = false): Board {
|
||||
public function find(int $id, bool $withLabels = false, bool $withAcl = false, bool $allowDeleted = false): Board {
|
||||
if (!isset($this->boardCache[$id])) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$deletedWhere = $allowDeleted ? $qb->expr()->gte('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)) : $qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT));
|
||||
$qb->select('*')
|
||||
->from('deck_boards')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($deletedWhere)
|
||||
->orderBy('id');
|
||||
$this->boardCache[$id] = $this->findEntity($qb);
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ class BoardService {
|
||||
*/
|
||||
public function setUserId(string $userId): void {
|
||||
$this->userId = $userId;
|
||||
$this->permissionService->setUserId($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +181,7 @@ class BoardService {
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
public function find($boardId) {
|
||||
public function find($boardId, bool $allowDeleted = false) {
|
||||
$this->boardServiceValidator->check(compact('boardId'));
|
||||
if ($this->boardsCache && isset($this->boardsCache[$boardId])) {
|
||||
return $this->boardsCache[$boardId];
|
||||
@@ -191,7 +192,7 @@ class BoardService {
|
||||
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||
/** @var Board $board */
|
||||
$board = $this->boardMapper->find($boardId, true, true);
|
||||
$board = $this->boardMapper->find((int)$boardId, true, true, $allowDeleted);
|
||||
$this->boardMapper->mapOwner($board);
|
||||
if ($board->getAcl() !== null) {
|
||||
foreach ($board->getAcl() as $acl) {
|
||||
@@ -366,7 +367,7 @@ class BoardService {
|
||||
$this->boardServiceValidator->check(compact('id'));
|
||||
|
||||
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
|
||||
$board = $this->find($id);
|
||||
$board = $this->find($id, true);
|
||||
$board->setDeletedAt(0);
|
||||
$board = $this->boardMapper->update($board);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $board, ActivityManager::SUBJECT_BOARD_RESTORE);
|
||||
@@ -387,7 +388,7 @@ class BoardService {
|
||||
$this->boardServiceValidator->check(compact('id'));
|
||||
|
||||
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
|
||||
$board = $this->find($id);
|
||||
$board = $this->find($id, true);
|
||||
$delete = $this->boardMapper->delete($board);
|
||||
|
||||
return $delete;
|
||||
@@ -470,7 +471,7 @@ class BoardService {
|
||||
$newAcl = $this->aclMapper->insert($acl);
|
||||
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId);
|
||||
$this->notificationHelper->sendBoardShared((int)$boardId, $acl);
|
||||
$this->notificationHelper->sendBoardShared($boardId, $acl);
|
||||
$this->boardMapper->mapAcl($newAcl);
|
||||
$this->changeHelper->boardChanged($boardId);
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ class CardService {
|
||||
public function update($id, $title, $stackId, $type, $owner, $description = '', $order = 0, $duedate = null, $deletedAt = null, $archived = null) {
|
||||
$this->cardServiceValidator->check(compact('id', 'title', 'stackId', 'type', 'owner', 'order'));
|
||||
|
||||
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
|
||||
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT, null, true);
|
||||
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
|
||||
|
||||
if ($this->boardService->isArchived($this->cardMapper, $id)) {
|
||||
@@ -274,6 +274,14 @@ class CardService {
|
||||
if ($archived !== null && $card->getArchived() && $archived === true) {
|
||||
throw new StatusException('Operation not allowed. This card is archived.');
|
||||
}
|
||||
|
||||
if ($card->getDeletedAt() !== 0) {
|
||||
if ($deletedAt === null || $deletedAt > 0) {
|
||||
// Only allow operations when restoring the card
|
||||
throw new NoPermissionException('Operation not allowed. This card was deleted.');
|
||||
}
|
||||
}
|
||||
|
||||
$changes = new ChangeSet($card);
|
||||
if ($card->getLastEditor() !== $this->currentUser && $card->getLastEditor() !== null) {
|
||||
$this->activityManager->triggerEvent(
|
||||
|
||||
@@ -83,24 +83,17 @@ class CommentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cardId
|
||||
* @param string $message
|
||||
* @param string $replyTo
|
||||
* @return DataResponse
|
||||
* @throws BadRequestException
|
||||
* @throws NotFoundException|NoPermissionException
|
||||
*/
|
||||
public function create(string $cardId, string $message, string $replyTo = '0'): DataResponse {
|
||||
if (!is_numeric($cardId)) {
|
||||
throw new BadRequestException('A valid card id must be provided');
|
||||
}
|
||||
public function create(int $cardId, string $message, string $replyTo = '0'): DataResponse {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||
|
||||
// Check if parent is a comment on the same card
|
||||
if ($replyTo !== '0') {
|
||||
try {
|
||||
$comment = $this->commentsManager->get($replyTo);
|
||||
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) {
|
||||
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$comment->getObjectId() !== $cardId) {
|
||||
throw new CommentNotFoundException();
|
||||
}
|
||||
} catch (CommentNotFoundException $e) {
|
||||
@@ -109,7 +102,7 @@ class CommentService {
|
||||
}
|
||||
|
||||
try {
|
||||
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, $cardId);
|
||||
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, (string)$cardId);
|
||||
$comment->setMessage($message);
|
||||
$comment->setVerb('comment');
|
||||
$comment->setParentId($replyTo);
|
||||
@@ -145,7 +138,7 @@ class CommentService {
|
||||
throw new NoPermissionException('Only authors are allowed to edit their comment.');
|
||||
}
|
||||
if ($comment->getParentId() !== '0') {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $comment->getParentId(), Acl::PERMISSION_READ);
|
||||
$this->permissionService->checkPermission($this->cardMapper, (int)$comment->getParentId(), Acl::PERMISSION_READ);
|
||||
}
|
||||
|
||||
$comment->setMessage($message);
|
||||
|
||||
@@ -199,4 +199,12 @@ class ConfigService {
|
||||
|
||||
return $this->config->getUserValue($userId ?? $this->getUserId(), 'deck', 'attachment_folder', '/Deck');
|
||||
}
|
||||
|
||||
public function setAttachmentFolder(?string $userId = null, string $path): void {
|
||||
if ($userId === null && $this->getUserId() === null) {
|
||||
throw new NoPermissionException('Must be logged in get the attachment folder');
|
||||
}
|
||||
|
||||
$this->config->setUserValue($userId ?? $this->getUserId(), 'deck', 'attachment_folder', $path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use OCA\Deck\Sharing\DeckShareProvider;
|
||||
use OCA\Deck\StatusException;
|
||||
use OCP\AppFramework\Http\StreamResponse;
|
||||
use OCP\Constants;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\IMimeTypeDetector;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
@@ -189,6 +190,16 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
||||
$folder = $userFolder->newFolder($this->configService->getAttachmentFolder());
|
||||
}
|
||||
|
||||
if ($folder->isShared()) {
|
||||
$folderName = $userFolder->getNonExistingName($this->configService->getAttachmentFolder());
|
||||
$folder = $userFolder->newFolder($folderName);
|
||||
$this->configService->setAttachmentFolder($this->userId, $folderName);
|
||||
}
|
||||
|
||||
if (!$folder instanceof Folder || $folder->isShared()) {
|
||||
throw new NotFoundException('No target folder found');
|
||||
}
|
||||
|
||||
$fileName = $folder->getNonExistingName($fileName);
|
||||
$target = $folder->newFile($fileName);
|
||||
$content = fopen($file['tmp_name'], 'rb');
|
||||
|
||||
@@ -29,6 +29,7 @@ use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\IPermissionMapper;
|
||||
use OCA\Deck\Db\User;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
@@ -97,13 +98,18 @@ class PermissionService {
|
||||
* @param $boardId
|
||||
* @return bool|array
|
||||
*/
|
||||
public function getPermissions($boardId) {
|
||||
public function getPermissions($boardId, ?string $userId = null) {
|
||||
if ($userId === null) {
|
||||
$userId = $this->userId;
|
||||
}
|
||||
|
||||
if ($cached = $this->permissionCache->get($boardId)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$owner = $this->userIsBoardOwner($boardId);
|
||||
$acls = $this->aclMapper->findAll($boardId);
|
||||
$board = $this->getBoard($boardId);
|
||||
$owner = $this->userIsBoardOwner($boardId, $userId);
|
||||
$acls = $board->getDeletedAt() === 0 ? $this->aclMapper->findAll($boardId) : [];
|
||||
$permissions = [
|
||||
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
|
||||
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
|
||||
@@ -111,7 +117,7 @@ class PermissionService {
|
||||
Acl::PERMISSION_SHARE => ($owner || $this->userCan($acls, Acl::PERMISSION_SHARE))
|
||||
&& (!$this->shareManager->sharingDisabledForUser($this->userId))
|
||||
];
|
||||
$this->permissionCache->set($boardId, $permissions);
|
||||
$this->permissionCache->set((string)$boardId, $permissions);
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
@@ -137,13 +143,10 @@ class PermissionService {
|
||||
/**
|
||||
* check permissions for replacing dark magic middleware
|
||||
*
|
||||
* @param $mapper IPermissionMapper|null null if $id is a boardId
|
||||
* @param $id int unique identifier of the Entity
|
||||
* @param $permission int
|
||||
* @return bool
|
||||
* @param numeric $id
|
||||
* @throws NoPermissionException
|
||||
*/
|
||||
public function checkPermission($mapper, $id, $permission, $userId = null) {
|
||||
public function checkPermission($mapper, $id, $permission, $userId = null, bool $allowDeletedCard = false) {
|
||||
$boardId = $id;
|
||||
if ($mapper instanceof IPermissionMapper && !($mapper instanceof BoardMapper)) {
|
||||
$boardId = $mapper->findBoardId($id);
|
||||
@@ -157,12 +160,20 @@ class PermissionService {
|
||||
throw new NoPermissionException('Permission denied');
|
||||
}
|
||||
|
||||
if ($this->userIsBoardOwner($boardId, $userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$acls = $this->getBoard($boardId)->getAcl() ?? [];
|
||||
$permissions = $this->getPermissions($boardId, $userId);
|
||||
if ($permissions[$permission] === true) {
|
||||
if (!$allowDeletedCard && $mapper instanceof CardMapper) {
|
||||
$card = $mapper->find($id);
|
||||
if ($card->getDeletedAt() > 0) {
|
||||
throw new NoPermissionException('Card is deleted');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$acls = $this->getBoard((int)$boardId)->getAcl() ?? [];
|
||||
$result = $this->userCan($acls, $permission, $userId);
|
||||
if ($result) {
|
||||
return true;
|
||||
@@ -194,11 +205,11 @@ class PermissionService {
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
private function getBoard($boardId): Board {
|
||||
if (!isset($this->boardCache[$boardId])) {
|
||||
$this->boardCache[$boardId] = $this->boardMapper->find($boardId, false, true);
|
||||
private function getBoard(int $boardId): Board {
|
||||
if (!isset($this->boardCache[(string)$boardId])) {
|
||||
$this->boardCache[(string)$boardId] = $this->boardMapper->find($boardId, false, true);
|
||||
}
|
||||
return $this->boardCache[$boardId];
|
||||
return $this->boardCache[(string)$boardId];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -115,7 +115,7 @@ class ShareAPIHelper {
|
||||
*/
|
||||
public function canAccessShare(IShare $share, string $user): bool {
|
||||
try {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $share->getSharedWith(), Acl::PERMISSION_READ, $user);
|
||||
$this->permissionService->checkPermission($this->cardMapper, (int)$share->getSharedWith(), Acl::PERMISSION_READ, $user);
|
||||
} catch (NoPermissionException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
6696
package-lock.json
generated
6696
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
196
package.json
196
package.json
@@ -1,100 +1,100 @@
|
||||
{
|
||||
"name": "deck",
|
||||
"description": "",
|
||||
"version": "1.7.4",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Julius Härtl",
|
||||
"email": "jus@bitgrid.net",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Michael Weimann",
|
||||
"email": "mail@michael-weimann.eu",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"license": "agpl",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production webpack --progress --config webpack.js",
|
||||
"dev": "NODE_ENV=development webpack --progress --config webpack.js",
|
||||
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint:fix": "eslint --ext .js,.vue src --fix",
|
||||
"stylelint": "stylelint src",
|
||||
"stylelint:fix": "stylelint src --fix",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/runtime": "^7.17.9",
|
||||
"@juliushaertl/vue-richtext": "^1.0.1",
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.9.0",
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
"@nextcloud/event-bus": "^2.1.1",
|
||||
"@nextcloud/files": "^2.1.0",
|
||||
"@nextcloud/initial-state": "^1.2.1",
|
||||
"@nextcloud/l10n": "^1.4.1",
|
||||
"@nextcloud/moment": "^1.2.0",
|
||||
"@nextcloud/router": "^2.0.0",
|
||||
"@nextcloud/vue": "^5.3.1",
|
||||
"@nextcloud/vue-dashboard": "^2.0.1",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"dompurify": "^2.3.6",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-task-checkbox": "^1.0.6",
|
||||
"moment": "^2.29.2",
|
||||
"nextcloud-vue-collections": "^0.9.0",
|
||||
"p-queue": "^6.6.2",
|
||||
"url-search-params-polyfill": "^8.1.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue-at": "^2.5.0-beta.2",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-easymde": "^2.0.0",
|
||||
"vue-infinite-loading": "^2.4.5",
|
||||
"vue-router": "^3.5.3",
|
||||
"vue-smooth-dnd": "^0.8.1",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"extends @nextcloud/browserslist-config"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^14.0.0",
|
||||
"npm": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextcloud/babel-config": "^1.0.0",
|
||||
"@nextcloud/browserslist-config": "^2.2.0",
|
||||
"@nextcloud/eslint-config": "^6.1.2",
|
||||
"@nextcloud/stylelint-config": "^2.1.2",
|
||||
"@nextcloud/webpack-vue-config": "^5.0.0",
|
||||
"@relative-ci/agent": "^3.1.2",
|
||||
"@vue/test-utils": "^1.3.0",
|
||||
"jest": "^27.5.1",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"vue-jest": "^3.0.7"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"vue"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^@/(.*)$": "<rootDir>/src/$1"
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
|
||||
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
|
||||
},
|
||||
"snapshotSerializers": [
|
||||
"<rootDir>/node_modules/jest-serializer-vue"
|
||||
]
|
||||
}
|
||||
"name": "deck",
|
||||
"description": "",
|
||||
"version": "1.7.5",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Julius Härtl",
|
||||
"email": "jus@bitgrid.net",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Michael Weimann",
|
||||
"email": "mail@michael-weimann.eu",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"license": "agpl",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production webpack --progress --config webpack.js",
|
||||
"dev": "NODE_ENV=development webpack --progress --config webpack.js",
|
||||
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint:fix": "eslint --ext .js,.vue src --fix",
|
||||
"stylelint": "stylelint src",
|
||||
"stylelint:fix": "stylelint src --fix",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/runtime": "^7.17.9",
|
||||
"@juliushaertl/vue-richtext": "^1.0.1",
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.9.0",
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
"@nextcloud/event-bus": "^2.1.1",
|
||||
"@nextcloud/files": "^2.1.0",
|
||||
"@nextcloud/initial-state": "^1.2.1",
|
||||
"@nextcloud/l10n": "^1.4.1",
|
||||
"@nextcloud/moment": "^1.2.0",
|
||||
"@nextcloud/router": "^2.0.0",
|
||||
"@nextcloud/vue": "^5.3.1",
|
||||
"@nextcloud/vue-dashboard": "^2.0.1",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"dompurify": "^2.3.6",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-task-checkbox": "^1.0.6",
|
||||
"moment": "^2.29.2",
|
||||
"nextcloud-vue-collections": "^0.9.0",
|
||||
"p-queue": "^6.6.2",
|
||||
"url-search-params-polyfill": "^8.1.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue-at": "^2.5.0-beta.2",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-easymde": "^2.0.0",
|
||||
"vue-infinite-loading": "^2.4.5",
|
||||
"vue-router": "^3.5.3",
|
||||
"vue-smooth-dnd": "^0.8.1",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"extends @nextcloud/browserslist-config"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^14.0.0",
|
||||
"npm": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextcloud/babel-config": "^1.0.0",
|
||||
"@nextcloud/browserslist-config": "^2.2.0",
|
||||
"@nextcloud/eslint-config": "^6.1.2",
|
||||
"@nextcloud/stylelint-config": "^2.1.2",
|
||||
"@nextcloud/webpack-vue-config": "^5.0.0",
|
||||
"@relative-ci/agent": "^3.1.2",
|
||||
"@vue/test-utils": "^1.3.0",
|
||||
"jest": "^27.5.1",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"vue-jest": "^3.0.7"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"vue"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^@/(.*)$": "<rootDir>/src/$1"
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
|
||||
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
|
||||
},
|
||||
"snapshotSerializers": [
|
||||
"<rootDir>/node_modules/jest-serializer-vue"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,20 @@ export default {
|
||||
params.append('object_id', '' + this.objectId)
|
||||
params.append('limit', ACTIVITY_FETCH_LIMIT)
|
||||
|
||||
const response = await axios.get(generateOcsUrl(`apps/activity/api/v2/activity/${this.filter}`) + '?' + params)
|
||||
const response = await axios.get(
|
||||
generateOcsUrl(`apps/activity/api/v2/activity/${this.filter}`) + '?' + params,
|
||||
{
|
||||
validateStatus: (status) => {
|
||||
return (status >= 200 && status < 300) || status === 304
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (response.status === 304) {
|
||||
this.endReached = true
|
||||
return []
|
||||
}
|
||||
|
||||
let activities = response.data.ocs.data
|
||||
if (this.filter === 'deck') {
|
||||
// We need to manually filter activities here, since currently we use two different types and there is no way
|
||||
@@ -95,7 +108,7 @@ export default {
|
||||
})
|
||||
}
|
||||
this.activities.push(...activities)
|
||||
if (response.data.ocs.meta.statuscode === 304 || activities.length === 0) {
|
||||
if (activities.length === 0) {
|
||||
this.endReached = true
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
:formatter="format"
|
||||
:disabled="saving || !canEdit"
|
||||
:shortcuts="shortcuts"
|
||||
:append-to-body="false"
|
||||
:append-to-body="true"
|
||||
confirm />
|
||||
<Actions v-if="canEdit">
|
||||
<ActionButton v-if="copiedCard.duedate" icon="icon-delete" @click="removeDue()">
|
||||
@@ -440,6 +440,6 @@ export default {
|
||||
<style>
|
||||
.mx-datepicker-main.mx-datepicker-popup {
|
||||
/* above the modal */
|
||||
z-index: 9999 !important;
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
||||
|
||||
1
tests/integration/base-query-count.txt
Normal file
1
tests/integration/base-query-count.txt
Normal file
@@ -0,0 +1 @@
|
||||
68024
|
||||
@@ -90,3 +90,53 @@ Feature: acl
|
||||
And the current user should not have "edit" permissions on the board
|
||||
And the current user should have "share" permissions on the board
|
||||
And the current user should not have "manage" permissions on the board
|
||||
|
||||
Scenario: Share a board multiple times
|
||||
Given Logging in using web as "user0"
|
||||
And creates a board named "Double shared board" with color "ff0000"
|
||||
And shares the board with user "user1"
|
||||
And shares the board with group "group1"
|
||||
And creates a board named "Single shared board" with color "00ff00"
|
||||
And shares the board with user "user1"
|
||||
When Logging in using web as "user1"
|
||||
And fetching the board list
|
||||
Then the response should have a status code "200"
|
||||
And the response should be a list of objects
|
||||
And the response should contain an element with the properties
|
||||
| property | value |
|
||||
| title | Double shared board |
|
||||
|
||||
|
||||
Scenario: Deleted board is inaccessible to share recipients
|
||||
Given acting as user "user0"
|
||||
When creates a board with example content
|
||||
And remember the last card as "user0-card"
|
||||
When post a comment with content "hello comment" on the card
|
||||
And uploads an attachment to the last used card
|
||||
And remember the last attachment as "user0-attachment"
|
||||
And shares the board with user "user1"
|
||||
Then the HTTP status code should be "200"
|
||||
And delete the board
|
||||
|
||||
Given acting as user "user1"
|
||||
When fetching the attachments for the card "user0-card"
|
||||
Then the response should have a status code 403
|
||||
|
||||
When get the comments on the card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When update a comment with content "hello deleted" on the card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When delete the comment on the card
|
||||
Then the response should have a status code 403
|
||||
# 644
|
||||
When post a comment with content "hello deleted" on the card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When get the card details
|
||||
Then the response should have a status code 403
|
||||
When fetching the attachment "user0-attachment" for the card "user0-card"
|
||||
Then the response should have a status code 403
|
||||
When deleting the attachment "user0-attachment" for the card "user0-card"
|
||||
Then the response should have a status code 403
|
||||
|
||||
@@ -87,4 +87,14 @@ class AttachmentContext implements Context {
|
||||
|
||||
$this->requestContext->sendPlainRequest('GET', '/index.php/apps/deck/cards/' . $cardId . '/attachment/file:' . $attachmentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When fetching the attachments for the card :cardReference
|
||||
*/
|
||||
public function fetchingTheAttachmentsForTheCard($cardReference) {
|
||||
$cardId = $this->boardContext->getRememberedCard($cardReference)['id'] ?? null;
|
||||
Assert::assertNotNull($cardId, 'Card needs to be available');
|
||||
|
||||
$this->requestContext->sendPlainRequest('GET', '/index.php/apps/deck/cards/' . $cardId . '/attachments');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ class BoardContext implements Context {
|
||||
/** @var array last card response */
|
||||
private $card = null;
|
||||
private array $storedCards = [];
|
||||
private ?array $activities = null;
|
||||
|
||||
/** @var ServerContext */
|
||||
private $serverContext;
|
||||
private ServerContext $serverContext;
|
||||
|
||||
/** @BeforeScenario */
|
||||
public function gatherContexts(BeforeScenarioScope $scope) {
|
||||
@@ -186,7 +186,9 @@ class BoardContext implements Context {
|
||||
['description' => $description]
|
||||
));
|
||||
$this->requestContext->getResponse()->getBody()->seek(0);
|
||||
$this->card = json_decode((string)$this->getResponse()->getBody(), true);
|
||||
if ($this->requestContext->getResponse()->getStatusCode() === 200) {
|
||||
$this->card = json_decode((string)$this->getResponse()->getBody(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,7 +200,22 @@ class BoardContext implements Context {
|
||||
[$attribute => $value]
|
||||
));
|
||||
$this->requestContext->getResponse()->getBody()->seek(0);
|
||||
$this->card = json_decode((string)$this->getResponse()->getBody(), true);
|
||||
if ($this->requestContext->getResponse()->getStatusCode() === 200) {
|
||||
$this->card = json_decode((string)$this->getResponse()->getBody(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^get the card details$/
|
||||
*/
|
||||
public function getCard() {
|
||||
$this->requestContext->sendJSONrequest('GET', '/index.php/apps/deck/cards/' . $this->card['id'], array_merge(
|
||||
$this->card
|
||||
));
|
||||
$this->requestContext->getResponse()->getBody()->seek(0);
|
||||
if ($this->requestContext->getResponse()->getStatusCode() === 200) {
|
||||
$this->card = json_decode((string)$this->getResponse()->getBody(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,4 +270,36 @@ class BoardContext implements Context {
|
||||
public function getRememberedCard($arg1) {
|
||||
return $this->storedCards[$arg1] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^delete the card$/
|
||||
*/
|
||||
public function deleteTheCard() {
|
||||
$this->requestContext->sendJSONrequest('DELETE', '/index.php/apps/deck/cards/' . $this->card['id']);
|
||||
$this->card['deletedAt'] = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^delete the board/
|
||||
*/
|
||||
public function deleteTheBoard() {
|
||||
$this->requestContext->sendJSONrequest('DELETE', '/index.php/apps/deck/boards/' . $this->board['id']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Given /^get the activities for the last card$/
|
||||
*/
|
||||
public function getActivitiesForTheLastCard() {
|
||||
$card = $this->getLastUsedCard();
|
||||
$this->requestContext->sendOCSRequest('GET', '/apps/activity/api/v2/activity/filter?format=json&type=deck&since=0&object_type=deck_card&object_id=' . $card['id'] . '&limit=50');
|
||||
$this->activities = json_decode((string)$this->getResponse()->getBody(), true)['ocs']['data'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the fetched activities should have :count entries
|
||||
*/
|
||||
public function theFetchedActivitiesShouldHaveEntries($count) {
|
||||
Assert::assertEquals($count, count($this->activities ?? []));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ class CommentContext implements Context {
|
||||
/** @var BoardContext */
|
||||
protected $boardContext;
|
||||
|
||||
private $lastComment = null;
|
||||
|
||||
/** @BeforeScenario */
|
||||
public function gatherContexts(BeforeScenarioScope $scope) {
|
||||
$environment = $scope->getEnvironment();
|
||||
@@ -27,5 +29,33 @@ class CommentContext implements Context {
|
||||
'message' => $content,
|
||||
'parentId' => null
|
||||
]);
|
||||
$this->lastComment = $this->requestContext->getResponseBodyFromJson()['ocs']['data'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^get the comments on the card$/
|
||||
*/
|
||||
public function getCommentsOnTheCard() {
|
||||
$card = $this->boardContext->getLastUsedCard();
|
||||
$this->requestContext->sendOCSRequest('GET', '/apps/deck/api/v1.0/cards/' . $card['id'] . '/comments');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^update a comment with content "([^"]*)" on the card$/
|
||||
*/
|
||||
public function updateACommentWithContentOnTheCard($content) {
|
||||
$card = $this->boardContext->getLastUsedCard();
|
||||
$this->requestContext->sendOCSRequest('PUT', '/apps/deck/api/v1.0/cards/' . $card['id'] . '/comments/'. $this->lastComment['id'], [
|
||||
'message' => $content,
|
||||
'parentId' => null
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^delete the comment on the card$/
|
||||
*/
|
||||
public function deleteTheCommentOnTheCard() {
|
||||
$card = $this->boardContext->getLastUsedCard();
|
||||
$this->requestContext->sendOCSRequest('DELETE', '/apps/deck/api/v1.0/cards/' . $card['id'] . '/comments/'. $this->lastComment['id']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,4 +166,29 @@ class RequestContext implements Context {
|
||||
$this->getResponse()->getBody()->seek(0);
|
||||
return json_decode((string)$this->getResponse()->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^the response should be a list of objects$/
|
||||
*/
|
||||
public function theResponseShouldBeAListOfObjects() {
|
||||
$jsonResponse = $this->getResponseBodyFromJson();
|
||||
Assert::assertEquals(array_keys($jsonResponse), range(0, count($jsonResponse) - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^the response should contain an element with the properties$/
|
||||
*/
|
||||
public function responseContainsElement(TableNode $element) {
|
||||
$json = $this->getResponseBodyFromJson();
|
||||
$found = array_filter($json, function ($board) use ($element) {
|
||||
foreach ($element as $row) {
|
||||
if ($row['value'] !== $board[$row['property']]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
Assert::assertEquals(1, count($found));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ class ServerContext implements Context {
|
||||
WebDav::__construct as private __tConstruct;
|
||||
}
|
||||
|
||||
private string $rawBaseUrl;
|
||||
private string $mappedUserId;
|
||||
private array $lastInsertIds = [];
|
||||
|
||||
public function __construct($baseUrl) {
|
||||
$this->rawBaseUrl = $baseUrl;
|
||||
$this->__tConstruct($baseUrl . '/index.php/ocs/', ['admin', 'admin'], '123456');
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
private $mappedUserId;
|
||||
|
||||
private $lastInsertIds = [];
|
||||
|
||||
/**
|
||||
* @BeforeSuite
|
||||
|
||||
@@ -32,3 +32,80 @@ Feature: decks
|
||||
And creates a board named "MyBoard" with color "000000"
|
||||
And create a stack named "ToDo"
|
||||
When create a card named "This is a very ong name that exceeds the maximum length of a deck board created which is longer than 255 characters This is a very ong name that exceeds the maximum length of a deck board created which is longer than 255 characters This is a very ong name that exceeds the maximum length of a deck board created which is longer than 255 characters"
|
||||
|
||||
Scenario: Cannot access card on a deleted board
|
||||
Given acting as user "user0"
|
||||
And creates a board named "MyBoard" with color "000000"
|
||||
And create a stack named "ToDo"
|
||||
And create a card named "Overdue task"
|
||||
And remember the last card as "deletedCard"
|
||||
And uploads an attachment to the last used card
|
||||
And remember the last attachment as "my-attachment"
|
||||
And post a comment with content "My first comment" on the card
|
||||
And delete the board
|
||||
|
||||
When fetching the attachment "my-attachment" for the card "deletedCard"
|
||||
Then the response should have a status code 403
|
||||
|
||||
When get the comments on the card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When post a comment with content "My second comment" on the card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When uploads an attachment to the last used card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When set the description to "Update some text"
|
||||
Then the response should have a status code 403
|
||||
|
||||
When get the card details
|
||||
Then the response should have a status code 403
|
||||
|
||||
When create a card named "Overdue task"
|
||||
Then the response should have a status code 403
|
||||
|
||||
When create a stack named "ToDo"
|
||||
Then the response should have a status code 403
|
||||
|
||||
Scenario: Cannot access card on a deleted card
|
||||
Given acting as user "user0"
|
||||
And creates a board named "MyBoard" with color "000000"
|
||||
And create a stack named "ToDo"
|
||||
And create a card named "Overdue task"
|
||||
And remember the last card as "deletedCard"
|
||||
And uploads an attachment to the last used card
|
||||
And remember the last attachment as "my-attachment"
|
||||
And post a comment with content "My first comment" on the card
|
||||
When get the activities for the last card
|
||||
Then the fetched activities should have 3 entries
|
||||
And delete the card
|
||||
|
||||
When get the activities for the last card
|
||||
Then the fetched activities should have 0 entries
|
||||
|
||||
When fetching the attachment "my-attachment" for the card "deletedCard"
|
||||
Then the response should have a status code 403
|
||||
|
||||
When get the comments on the card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When post a comment with content "My second comment" on the card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When deleting the attachment "my-attachment" for the card "deletedCard"
|
||||
Then the response should have a status code 403
|
||||
|
||||
When uploads an attachment to the last used card
|
||||
Then the response should have a status code 403
|
||||
|
||||
When get the card details
|
||||
Then the response should have a status code 403
|
||||
|
||||
# We currently still expect to be able to update the card as this is used to undo deletion
|
||||
When set the description to "Update some text"
|
||||
Then the response should have a status code 403
|
||||
When set the card attribute "deletedAt" to "0"
|
||||
Then the response should have a status code 200
|
||||
When set the description to "Update some text"
|
||||
Then the response should have a status code 200
|
||||
|
||||
@@ -131,6 +131,7 @@ class ActivityManagerTest extends TestCase {
|
||||
|
||||
public function testCreateEvent() {
|
||||
$board = new Board();
|
||||
$board->setId(123);
|
||||
$board->setTitle('');
|
||||
$this->boardMapper->expects(self::once())
|
||||
->method('find')
|
||||
@@ -148,6 +149,7 @@ class ActivityManagerTest extends TestCase {
|
||||
|
||||
public function testCreateEventDescription() {
|
||||
$board = new Board();
|
||||
$board->setId(123);
|
||||
$board->setTitle('');
|
||||
$this->boardMapper->expects(self::once())
|
||||
->method('find')
|
||||
@@ -162,7 +164,9 @@ class ActivityManagerTest extends TestCase {
|
||||
->method('find')
|
||||
->willReturn($card);
|
||||
|
||||
$stack = Stack::fromRow([]);
|
||||
$stack = Stack::fromRow([
|
||||
'boardId' => 123,
|
||||
]);
|
||||
$this->stackMapper->expects(self::any())
|
||||
->method('find')
|
||||
->willReturn($stack);
|
||||
@@ -192,6 +196,7 @@ class ActivityManagerTest extends TestCase {
|
||||
|
||||
public function testCreateEventLongDescription() {
|
||||
$board = new Board();
|
||||
$board->setId(123);
|
||||
$board->setTitle('');
|
||||
$this->boardMapper->expects(self::once())
|
||||
->method('find')
|
||||
@@ -205,7 +210,9 @@ class ActivityManagerTest extends TestCase {
|
||||
->method('find')
|
||||
->willReturn($card);
|
||||
|
||||
$stack = new Stack();
|
||||
$stack = Stack::fromRow([
|
||||
'boardId' => 123,
|
||||
]);
|
||||
$this->stackMapper->expects(self::any())
|
||||
->method('find')
|
||||
->willReturn($stack);
|
||||
@@ -235,6 +242,7 @@ class ActivityManagerTest extends TestCase {
|
||||
|
||||
public function testCreateEventLabel() {
|
||||
$board = Board::fromRow([
|
||||
'id' => 123,
|
||||
'title' => 'My board'
|
||||
]);
|
||||
$this->boardMapper->expects(self::once())
|
||||
@@ -249,7 +257,9 @@ class ActivityManagerTest extends TestCase {
|
||||
->method('find')
|
||||
->willReturn($card);
|
||||
|
||||
$stack = Stack::fromParams([]);
|
||||
$stack = Stack::fromRow([
|
||||
'boardId' => 123,
|
||||
]);
|
||||
$this->stackMapper->expects(self::any())
|
||||
->method('find')
|
||||
->willReturn($stack);
|
||||
|
||||
@@ -74,6 +74,9 @@ class DeckProviderTest extends TestCase {
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->cardService = $this->createMock(CardService::class);
|
||||
$this->provider = new DeckProvider($this->urlGenerator, $this->activityManager, $this->userManager, $this->commentsManager, $this->l10nFactory, $this->config, $this->userId, $this->cardService);
|
||||
|
||||
$this->activityManager->method('canSeeCardActivity')->willReturn(true);
|
||||
$this->activityManager->method('canSeeBoardActivity')->willReturn(true);
|
||||
}
|
||||
|
||||
private function mockEvent($objectType, $objectId, $objectName, $subject, $subjectParameters = []) {
|
||||
|
||||
@@ -240,6 +240,8 @@ class PermissionServiceTest extends \Test\TestCase {
|
||||
->method('sharingDisabledForUser')
|
||||
->willReturn(false);
|
||||
|
||||
$this->aclMapper->method('findAll')->willReturn([]);
|
||||
|
||||
if ($result) {
|
||||
$actual = $this->service->checkPermission($mapper, 1234, $permission);
|
||||
$this->assertTrue($actual);
|
||||
@@ -262,6 +264,8 @@ class PermissionServiceTest extends \Test\TestCase {
|
||||
$this->boardMapper->expects($this->any())->method('find')->willReturn($board);
|
||||
}
|
||||
|
||||
$this->aclMapper->method('findAll')->willReturn([]);
|
||||
|
||||
if ($result) {
|
||||
$actual = $this->service->checkPermission($mapper, 1234, $permission);
|
||||
$this->assertTrue($actual);
|
||||
|
||||
Reference in New Issue
Block a user