Compare commits

..

57 Commits

Author SHA1 Message Date
grnd-alt
93307bb247 Merge pull request #6520 from nextcloud/backport/6518/stable24
[stable24] fix: Detect end of the activity responses (fix #3395)
2024-11-19 11:33:56 +01:00
Julius Härtl
45f2eedafe fix: Detect end of the activity responses (fix #3395)
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-11-19 10:24:54 +00:00
Arthur Schiwon
6fb0f6209c Merge pull request #5706 from nextcloud/backport/5703/stable24
[stable24] fix: Avoid conflicts on deck attachments folder name
2024-05-24 10:27:45 +02:00
Julius Härtl
043327dc09 fix: Compatibility with php 7.4
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-03-28 10:15:56 +01:00
Julius Härtl
05aafd9ddb ci: Update base query count
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-03-27 15:02:29 +00:00
Julius Härtl
5f789962b4 fix: Avoid conflicts on deck attachments folder name
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-03-27 15:02:28 +00:00
Julius Härtl
ff2c11c796 chore(release): Bump version to 1.7.5
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-16 17:37:35 +01:00
Julius Härtl
3dee8587b7 Merge pull request #5449 from nextcloud/backport/5444/stable24
[stable24] Fix deleted card/board issues
2024-01-12 09:30:35 +01:00
Julius Härtl
86751c4b89 test: Adapt unit tests
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-12 09:17:25 +01:00
Julius Härtl
f70b31c449 ci: Remove unused test
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-12 08:42:15 +01:00
Julius Härtl
52c5020ba4 chore: update actions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-12 08:42:15 +01:00
Julius Härtl
1f71d1ab78 fix: PHP 7.4 compatibility
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-12 08:42:15 +01:00
Julius Härtl
9119017ce8 tests: Fix missing behat context methods
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:06:21 +01:00
Julius Härtl
0c0bb40515 chore: Fix ci setup for activity
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:06:21 +01:00
Julius Härtl
0954d4d8a0 fix: Limit card activities for deleted cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:06:21 +01:00
Julius Härtl
272da5406a fix: Further limit updating cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:06:19 +01:00
Julius Härtl
aef06d833d fix: limit to non-deleted cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:05:43 +01:00
Julius Härtl
71948d670e fix: Consider a deleted board inaccessible to share recipients
Only the owner can delete/undo a board deletion so there is no reason
other users should have any permission on a board marked as deleted

Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:05:10 +01:00
Julius Härtl
22a3efe445 tests: Add integration tests for deleted boards/cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 23:05:09 +01:00
Julius Härtl
6aca034222 Merge pull request #5446 from nextcloud/backport/5440/stable24
[stable24] Fix small issues around delete/undo
2024-01-09 22:52:18 +01:00
Julius Härtl
b37ba6a409 ci: Update actions
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 22:15:17 +01:00
Julius Härtl
ffb99b8010 ci: Bump setup-php
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 19:38:33 +01:00
Julius Härtl
7e33f38b4c fix: Psalm and CI
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 19:38:09 +01:00
Julius Härtl
5391689eb0 ci: Update phpunit github action
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 19:38:09 +01:00
Julius Härtl
e289e05c98 fix: Only query boards not marked for deletion unless we want to undo
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2024-01-09 19:38:06 +01:00
Julius Härtl
efb0bef5e1 Merge pull request #5278 from nextcloud/automated/noid/stable24-fix-npm-audit 2023-11-12 09:06:49 +01:00
nextcloud-command
1825ae4711 chore(deps): fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2023-11-12 04:06:39 +00:00
Julius Härtl
c4bdffbce5 Merge pull request #5247 from nextcloud/automated/noid/stable24-fix-npm-audit 2023-10-29 20:29:37 +01:00
nextcloud-command
7eb57b0ed1 chore(deps): fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2023-10-29 03:37:32 +00:00
Julius Härtl
3516e7a9c2 Merge pull request #5230 from nextcloud/automated/noid/stable24-fix-npm-audit 2023-10-23 08:35:52 +02:00
nextcloud-command
5a5ee3cd4f chore(deps): fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2023-10-22 03:22:47 +00:00
Julius Härtl
574aa325a0 Merge pull request #5199 from nextcloud/dependabot/npm_and_yarn/stable24/babel/runtime-7.23.2
Chore(deps): Bump @babel/runtime from 7.22.15 to 7.23.2
2023-10-16 23:00:28 +02:00
dependabot[bot]
933e0b097e Chore(deps): Bump @babel/runtime from 7.22.15 to 7.23.2
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.22.15 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-14 01:17:32 +00:00
Julius Härtl
2a6023cf86 Merge pull request #5194 from nextcloud/automated/noid/stable24-fix-npm-audit 2023-10-09 12:30:17 +02:00
nextcloud-command
b1ee1ae156 chore(deps): fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2023-10-08 03:43:35 +00:00
Julius Härtl
892727fed0 Merge pull request #5112 from nextcloud/dependabot/npm_and_yarn/stable24/url-search-params-polyfill-8.2.5 2023-09-09 09:38:07 +02:00
Julius Härtl
cced84af3d Merge pull request #5113 from nextcloud/dependabot/npm_and_yarn/stable24/babel/runtime-7.22.15 2023-09-09 09:08:01 +02:00
dependabot[bot]
f33477fe1a Chore(deps): Bump @babel/runtime from 7.22.11 to 7.22.15
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.22.11 to 7.22.15.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.15/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-09 01:51:23 +00:00
dependabot[bot]
bafc3e5221 Chore(deps): Bump url-search-params-polyfill from 8.2.4 to 8.2.5
Bumps [url-search-params-polyfill](https://github.com/jerrybendy/url-search-params-polyfill) from 8.2.4 to 8.2.5.
- [Release notes](https://github.com/jerrybendy/url-search-params-polyfill/releases)
- [Commits](https://github.com/jerrybendy/url-search-params-polyfill/commits)

---
updated-dependencies:
- dependency-name: url-search-params-polyfill
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-09 01:51:10 +00:00
Julius Härtl
67ecf24d86 Merge pull request #5074 from nextcloud/backport/4810/stable24 2023-08-30 19:44:31 +02:00
Max
10bda7253e Fix(occ): set user id for permission sevice from board service
Fixes #4010.

Signed-off-by: Max <max@nextcloud.com>
2023-08-30 08:51:09 +00:00
Julius Härtl
1713343dd4 Merge pull request #5063 from nextcloud/dependabot/npm_and_yarn/stable24/babel/runtime-7.22.11 2023-08-28 08:30:34 +02:00
dependabot[bot]
c2cf3feb25 Chore(deps): Bump @babel/runtime from 7.22.10 to 7.22.11
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.22.10 to 7.22.11.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.11/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-26 01:24:10 +00:00
Julius Härtl
56792e048a Merge pull request #5020 from nextcloud/dependabot/npm_and_yarn/stable24/babel/runtime-7.22.10 2023-08-12 10:02:13 +02:00
dependabot[bot]
dae3144c25 Chore(deps): Bump @babel/runtime from 7.22.6 to 7.22.10
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.22.6 to 7.22.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-12 02:19:24 +00:00
Julius Härtl
31b73fe6bb Merge pull request #4983 from nextcloud/dependabot/npm_and_yarn/stable24/dompurify-2.4.7 2023-08-06 22:10:53 +02:00
dependabot[bot]
ef686bef34 Chore(deps): Bump dompurify from 2.4.5 to 2.4.7
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.4.5 to 2.4.7.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.4.5...2.4.7)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-05 02:19:00 +00:00
Julius Härtl
50609944bc Merge pull request #4959 from nextcloud/automated/noid/stable24-fix-npm-audit 2023-07-24 08:25:32 +02:00
nextcloud-command
21727cfb3a chore(deps): fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2023-07-23 03:20:18 +00:00
Julius Härtl
a93b874b89 Merge pull request #4872 from nextcloud/automated/noid/stable24-fix-npm-audit 2023-07-10 12:31:24 +02:00
Julius Härtl
c573c33926 Merge pull request #4888 from nextcloud/dependabot/npm_and_yarn/stable24/babel/runtime-7.22.6 2023-07-10 07:30:05 +02:00
Julius Härtl
9ff9858e9e Merge pull request #4889 from nextcloud/dependabot/npm_and_yarn/stable24/url-search-params-polyfill-8.2.4 2023-07-10 07:29:54 +02:00
nextcloud-command
aa3c80e8d0 chore(deps): fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2023-07-09 03:53:19 +00:00
dependabot[bot]
56c4d45712 Chore(deps): Bump url-search-params-polyfill from 8.2.2 to 8.2.4
Bumps [url-search-params-polyfill](https://github.com/jerrybendy/url-search-params-polyfill) from 8.2.2 to 8.2.4.
- [Release notes](https://github.com/jerrybendy/url-search-params-polyfill/releases)
- [Commits](https://github.com/jerrybendy/url-search-params-polyfill/commits/v8.2.4)

---
updated-dependencies:
- dependency-name: url-search-params-polyfill
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-08 01:27:33 +00:00
dependabot[bot]
4f67d2fe60 Chore(deps): Bump @babel/runtime from 7.22.5 to 7.22.6
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.22.5 to 7.22.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.6/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-08 01:27:25 +00:00
Julius Härtl
875ac0d6e9 Merge pull request #4851 from nextcloud/automated/noid/stable24-fix-npm-audit 2023-06-26 16:29:47 +02:00
nextcloud-command
741df20b49 chore(deps): fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2023-06-25 03:57:37 +00:00
33 changed files with 4122 additions and 3303 deletions

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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: |

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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": {

View File

@@ -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;
}
}
}

View File

@@ -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']);
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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(

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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');

View File

@@ -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];
}
/**

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
]
}
}

View File

@@ -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 []
}

View File

@@ -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>

View File

@@ -0,0 +1 @@
68024

View File

@@ -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

View File

@@ -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');
}
}

View File

@@ -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 ?? []));
}
}

View File

@@ -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']);
}
}

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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 = []) {

View File

@@ -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);