diff --git a/app/Helper/functions.php b/app/Helper/functions.php index 599a85487..eb781fe02 100644 --- a/app/Helper/functions.php +++ b/app/Helper/functions.php @@ -33,7 +33,8 @@ function setupCompleted(): ?bool */ function usersettings(string $key = '', ?int $userId = null): mixed { - if (is_null($userId) && !auth()->user()) { + // return null if no user id is passed, no user is authenticated, or the system user is being used via the API + if ((is_null($userId) && !auth()->user()) || auth()->id() === 0) { return null; } diff --git a/app/Http/Controllers/API/LinkNotesController.php b/app/Http/Controllers/API/LinkNotesController.php index b399eb750..df43bb1cb 100644 --- a/app/Http/Controllers/API/LinkNotesController.php +++ b/app/Http/Controllers/API/LinkNotesController.php @@ -3,13 +3,13 @@ namespace App\Http\Controllers\API; use App\Http\Controllers\Controller; -use App\Models\Link; +use App\Models\Api\ApiLink; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class LinkNotesController extends Controller { - public function __invoke(Request $request, Link $link): JsonResponse + public function __invoke(Request $request, ApiLink $link): JsonResponse { if ($request->user()->cannot('view', $link)) { return response()->json(status: 403); diff --git a/app/Http/Controllers/API/ListController.php b/app/Http/Controllers/API/ListController.php index d9bb129df..4f13212f0 100644 --- a/app/Http/Controllers/API/ListController.php +++ b/app/Http/Controllers/API/ListController.php @@ -7,7 +7,6 @@ use App\Http\Requests\Models\ListStoreRequest; use App\Http\Requests\Models\ListUpdateRequest; use App\Models\Api\ApiLinkList; -use App\Models\LinkList; use App\Repositories\ListRepository; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -29,7 +28,7 @@ public function index(Request $request): JsonResponse $this->checkOrdering(); - $lists = LinkList::query() + $lists = ApiLinkList::query() ->visibleForUser() ->orderBy($this->orderBy, $this->orderDir) ->paginate(getPaginationLimit()); @@ -44,7 +43,7 @@ public function store(ListStoreRequest $request): JsonResponse return response()->json($link); } - public function show(LinkList $list): JsonResponse + public function show(ApiLinkList $list): JsonResponse { // Instead of displaying all links for that list, show the URL to directly fetch all links for that list $list->setAttribute('links', route('api.lists.links', ['list' => $list], true)); @@ -52,14 +51,14 @@ public function show(LinkList $list): JsonResponse return response()->json($list); } - public function update(ListUpdateRequest $request, LinkList $list): JsonResponse + public function update(ListUpdateRequest $request, ApiLinkList $list): JsonResponse { $updatedList = ListRepository::update($list, $request->all()); return response()->json($updatedList); } - public function destroy(LinkList $list): JsonResponse + public function destroy(ApiLinkList $list): JsonResponse { $deletionSuccessful = ListRepository::delete($list); diff --git a/app/Http/Controllers/API/ListLinksController.php b/app/Http/Controllers/API/ListLinksController.php index 34436a036..d6e4c3fab 100644 --- a/app/Http/Controllers/API/ListLinksController.php +++ b/app/Http/Controllers/API/ListLinksController.php @@ -3,13 +3,13 @@ namespace App\Http\Controllers\API; use App\Http\Controllers\Controller; -use App\Models\LinkList; +use App\Models\Api\ApiLinkList; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class ListLinksController extends Controller { - public function __invoke(Request $request, LinkList $list): JsonResponse + public function __invoke(Request $request, ApiLinkList $list): JsonResponse { if ($request->user()->cannot('view', $list)) { return response()->json(status: 403); diff --git a/app/Http/Controllers/API/NoteController.php b/app/Http/Controllers/API/NoteController.php index bfc285dba..a2bc709eb 100644 --- a/app/Http/Controllers/API/NoteController.php +++ b/app/Http/Controllers/API/NoteController.php @@ -6,7 +6,6 @@ use App\Http\Requests\Models\NoteStoreRequest; use App\Http\Requests\Models\NoteUpdateRequest; use App\Models\Api\ApiNote; -use App\Models\Note; use App\Repositories\NoteRepository; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; @@ -25,14 +24,14 @@ public function store(NoteStoreRequest $request): JsonResponse return response()->json($note); } - public function update(NoteUpdateRequest $request, Note $note): JsonResponse + public function update(NoteUpdateRequest $request, ApiNote $note): JsonResponse { $updatedNote = NoteRepository::update($note, $request->validated()); return response()->json($updatedNote); } - public function destroy(Note $note): JsonResponse + public function destroy(ApiNote $note): JsonResponse { $deletionSuccessful = NoteRepository::delete($note); diff --git a/app/Http/Controllers/API/TagController.php b/app/Http/Controllers/API/TagController.php index 48e1db1d3..aa67acfe8 100644 --- a/app/Http/Controllers/API/TagController.php +++ b/app/Http/Controllers/API/TagController.php @@ -7,7 +7,6 @@ use App\Http\Requests\Models\TagStoreRequest; use App\Http\Requests\Models\TagUpdateRequest; use App\Models\Api\ApiTag; -use App\Models\Tag; use App\Repositories\TagRepository; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -29,7 +28,7 @@ public function index(Request $request): JsonResponse $this->checkOrdering(); - $tags = Tag::query() + $tags = ApiTag::query() ->visibleForUser() ->orderBy($this->orderBy, $this->orderDir) ->paginate(getPaginationLimit()); @@ -44,19 +43,19 @@ public function store(TagStoreRequest $request): JsonResponse return response()->json($tag); } - public function show(Tag $tag): JsonResponse + public function show(ApiTag $tag): JsonResponse { return response()->json($tag); } - public function update(TagUpdateRequest $request, Tag $tag): JsonResponse + public function update(TagUpdateRequest $request, ApiTag $tag): JsonResponse { $updatedTag = TagRepository::update($tag, $request->all()); return response()->json($updatedTag); } - public function destroy(Tag $tag): JsonResponse + public function destroy(ApiTag $tag): JsonResponse { $deletionSuccessful = TagRepository::delete($tag); diff --git a/app/Http/Controllers/API/TagLinksController.php b/app/Http/Controllers/API/TagLinksController.php index 89f6272b4..dc0ca3dee 100644 --- a/app/Http/Controllers/API/TagLinksController.php +++ b/app/Http/Controllers/API/TagLinksController.php @@ -3,13 +3,13 @@ namespace App\Http\Controllers\API; use App\Http\Controllers\Controller; -use App\Models\Tag; +use App\Models\Api\ApiTag; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class TagLinksController extends Controller { - public function __invoke(Request $request, Tag $tag): JsonResponse + public function __invoke(Request $request, ApiTag $tag): JsonResponse { if ($request->user()->cannot('view', $tag)) { return response()->json(status: 403); diff --git a/app/Http/Controllers/API/TrashController.php b/app/Http/Controllers/API/TrashController.php index 80f02bbc7..64a38bb9f 100644 --- a/app/Http/Controllers/API/TrashController.php +++ b/app/Http/Controllers/API/TrashController.php @@ -5,10 +5,10 @@ use App\Http\Controllers\Controller; use App\Http\Requests\TrashClearRequest; use App\Http\Requests\TrashRestoreRequest; -use App\Models\Link; -use App\Models\LinkList; -use App\Models\Note; -use App\Models\Tag; +use App\Models\Api\ApiLink; +use App\Models\Api\ApiLinkList; +use App\Models\Api\ApiNote; +use App\Models\Api\ApiTag; use App\Repositories\TrashRepository; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -17,7 +17,7 @@ class TrashController extends Controller { public function getLinks(Request $request): JsonResponse { - $links = Link::onlyTrashed() + $links = ApiLink::onlyTrashed() ->byUser($request->user()->id) ->get(); @@ -26,7 +26,7 @@ public function getLinks(Request $request): JsonResponse public function getLists(Request $request): JsonResponse { - $lists = LinkList::onlyTrashed() + $lists = ApiLinkList::onlyTrashed() ->byUser($request->user()->id) ->get(); @@ -35,7 +35,7 @@ public function getLists(Request $request): JsonResponse public function getTags(Request $request): JsonResponse { - $tags = Tag::onlyTrashed() + $tags = ApiTag::onlyTrashed() ->byUser($request->user()->id) ->get(); @@ -44,7 +44,7 @@ public function getTags(Request $request): JsonResponse public function getNotes(Request $request): JsonResponse { - $notes = Note::onlyTrashed() + $notes = ApiNote::onlyTrashed() ->byUser($request->user()->id) ->get(); diff --git a/app/Policies/Api/ApiLinkPolicy.php b/app/Policies/Api/ApiLinkPolicy.php index 26692d32f..b7ca3ba31 100644 --- a/app/Policies/Api/ApiLinkPolicy.php +++ b/app/Policies/Api/ApiLinkPolicy.php @@ -41,16 +41,16 @@ public function update(User $user, ApiLink $link): bool public function delete(User $user, ApiLink $link): bool { - return $link->user->is($user); + return $this->userCanDeleteModel($user, $link); } public function restore(User $user, ApiLink $link): bool { - return $link->user->is($user); + return $this->userCanUpdateModel($user, $link); } public function forceDelete(User $user, ApiLink $link): bool { - return $link->user->is($user); + return $this->userCanDeleteModel($user, $link); } } diff --git a/app/Policies/Api/AuthorizesUserApiActions.php b/app/Policies/Api/AuthorizesUserApiActions.php index 7ce30c4a5..9811494b0 100644 --- a/app/Policies/Api/AuthorizesUserApiActions.php +++ b/app/Policies/Api/AuthorizesUserApiActions.php @@ -36,4 +36,18 @@ protected function userCanUpdateModel(User $user, Model $model): bool } return $model->visibility !== ModelAttribute::VISIBILITY_PRIVATE; } + + protected function userCanDeleteModel(User $user, Model $model): bool + { + if ($model->user_id === $user->id) { + return true; + } + if ($user->isSystemUser()) { + if ($model->visibility === ModelAttribute::VISIBILITY_PRIVATE) { + return $user->tokenCan($this->deleteAbility) && $user->tokenCan(ApiToken::ABILITY_SYSTEM_ACCESS_PRIVATE); + } + return $user->tokenCan($this->deleteAbility); + } + return false; + } } diff --git a/app/Policies/Api/LinkListApiPolicy.php b/app/Policies/Api/LinkListApiPolicy.php index 7887d1e3e..bf35ac250 100644 --- a/app/Policies/Api/LinkListApiPolicy.php +++ b/app/Policies/Api/LinkListApiPolicy.php @@ -18,6 +18,9 @@ class LinkListApiPolicy public function viewAny(User $user): bool { + if ($user->isSystemUser()) { + return $user->tokenCan(ApiToken::ABILITY_LISTS_READ); + } return true; } @@ -38,16 +41,16 @@ public function update(User $user, ApiLinkList $list): bool public function delete(User $user, ApiLinkList $list): bool { - return $list->user->is($user); + return $this->userCanDeleteModel($user, $list); } public function restore(User $user, ApiLinkList $list): bool { - return $list->user->is($user); + return $this->userCanUpdateModel($user, $list); } public function forceDelete(User $user, ApiLinkList $list): bool { - return $list->user->is($user); + return $this->userCanDeleteModel($user, $list); } } diff --git a/app/Policies/Api/NoteApiPolicy.php b/app/Policies/Api/NoteApiPolicy.php index bbf754cad..344a74668 100644 --- a/app/Policies/Api/NoteApiPolicy.php +++ b/app/Policies/Api/NoteApiPolicy.php @@ -18,12 +18,15 @@ class NoteApiPolicy public function viewAny(User $user): bool { + if ($user->isSystemUser()) { + return $user->tokenCan(ApiToken::ABILITY_NOTES_READ); + } return true; } public function view(User $user, Note $note): bool { - return $this->userCanAccessNote($user, $note); + return $this->userCanAccessModel($user, $note); } public function create(User $user): bool @@ -33,21 +36,21 @@ public function create(User $user): bool public function update(User $user, Note $note): bool { - return $this->userCanAccessNote($user, $note); + return $this->userCanUpdateModel($user, $note); } public function delete(User $user, Note $note): bool { - return $note->user->is($user); + return $this->userCanDeleteModel($user, $note); } public function restore(User $user, Note $note): bool { - return $note->user->is($user); + return $this->userCanUpdateModel($user, $note); } public function forceDelete(User $user, Note $note): bool { - return $note->user->is($user); + return $this->userCanDeleteModel($user, $note); } } diff --git a/app/Policies/Api/TagApiPolicy.php b/app/Policies/Api/TagApiPolicy.php index c67cf1f67..927ebda44 100644 --- a/app/Policies/Api/TagApiPolicy.php +++ b/app/Policies/Api/TagApiPolicy.php @@ -18,6 +18,9 @@ class TagApiPolicy public function viewAny(User $user): bool { + if ($user->isSystemUser()) { + return $user->tokenCan(ApiToken::ABILITY_TAGS_READ); + } return true; } @@ -38,16 +41,16 @@ public function update(User $user, Tag $tag): bool public function delete(User $user, Tag $tag): bool { - return $tag->user->is($user); + return $this->userCanDeleteModel($user, $tag); } public function restore(User $user, Tag $tag): bool { - return $tag->user->is($user); + return $this->userCanUpdateModel($user, $tag); } public function forceDelete(User $user, Tag $tag): bool { - return $tag->user->is($user); + return $this->userCanDeleteModel($user, $tag); } } diff --git a/app/Repositories/LinkRepository.php b/app/Repositories/LinkRepository.php index 31c00ea46..69d6c47b5 100644 --- a/app/Repositories/LinkRepository.php +++ b/app/Repositories/LinkRepository.php @@ -2,6 +2,7 @@ namespace App\Repositories; +use App\Enums\ModelAttribute; use App\Helper\HtmlMeta; use App\Helper\LinkIconMapper; use App\Models\Link; @@ -223,8 +224,8 @@ protected static function processTaxonomy(string $model, array $entries): Collec $newEntries = collect(); $visibilitySetting = match ($model) { - Tag::class => usersettings('tags_default_visibility'), - LinkList::class => usersettings('lists_default_visibility'), + Tag::class => usersettings('tags_default_visibility') ?? ModelAttribute::VISIBILITY_INTERNAL, + LinkList::class => usersettings('lists_default_visibility') ?? ModelAttribute::VISIBILITY_INTERNAL, }; foreach ($entries as $entry) { diff --git a/app/Repositories/NoteRepository.php b/app/Repositories/NoteRepository.php index fc3abb859..df84cb6fc 100644 --- a/app/Repositories/NoteRepository.php +++ b/app/Repositories/NoteRepository.php @@ -10,7 +10,7 @@ class NoteRepository { public static function create(array $data): Note { - $data['user_id'] = auth()->user()->id; + $data['user_id'] = auth()->id(); return Note::create($data); } diff --git a/resources/assets/js/components/TagsSelect.js b/resources/assets/js/components/TagsSelect.js index 80f7591ab..15fad7d33 100644 --- a/resources/assets/js/components/TagsSelect.js +++ b/resources/assets/js/components/TagsSelect.js @@ -42,7 +42,8 @@ export default class TagsSelect { }, onChange: function () { const items = this.items.map((item) => { - const option = Object.values(this.options).find((option) => option.id === parseInt(item)); + item = (typeof item === 'string' && /^\d+$/.test(item)) ? Number(item) : item; + const option = Object.values(this.options).find((option) => option.id === item); return option !== undefined ? option.id : item; }); selectObject.$el.value = items.length > 0 ? JSON.stringify(items) : null; diff --git a/resources/docker/dockerfiles/development.Dockerfile b/resources/docker/dockerfiles/development.Dockerfile index 715b0e5d5..8bc0303fd 100644 --- a/resources/docker/dockerfiles/development.Dockerfile +++ b/resources/docker/dockerfiles/development.Dockerfile @@ -5,13 +5,14 @@ FROM docker.io/library/php:8.1-fpm-alpine WORKDIR /app # Install package and PHP dependencies -RUN apk add --no-cache zip git mariadb-client postgresql-client postgresql-dev sqlite zip libzip-dev linux-headers; \ - docker-php-ext-install bcmath pdo_mysql pdo_pgsql zip ftp sockets; \ - docker-php-ext-enable xdebug pcov; \ - mkdir /ssl-certs; \ - docker-php-source delete; \ - rm -f /usr/src/php.tar.xz /usr/src/php.tar.xz.asc; \ - apk del --no-cache postgresql-dev +RUN apk add --no-cache zip git mariadb-client postgresql-client postgresql-dev sqlite zip libzip-dev linux-headers autoconf make \ + && pecl install xdebug pcov \ + && docker-php-ext-install bcmath pdo_mysql pdo_pgsql zip ftp sockets \ + && docker-php-ext-enable xdebug pcov \ + && mkdir /ssl-certs \ + && docker-php-source delete \ + && rm -f /usr/src/php.tar.xz /usr/src/php.tar.xz.asc \ + && apk del --no-cache postgresql-dev autoconf make RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer diff --git a/resources/docker/php/php-dev.ini b/resources/docker/php/php-dev.ini index 44cabc26d..3fe38014a 100644 --- a/resources/docker/php/php-dev.ini +++ b/resources/docker/php/php-dev.ini @@ -18,7 +18,7 @@ post_max_size = 20M xdebug.mode = profile xdebug.client_host = docker.for.mac.localhost -xdebug.client_port = 9003 +xdebug.client_port = 10000 xdebug.idekey = docker xdebug.start_with_request = yes xdebug.log = /tmp/xdebug_remote.log diff --git a/tests/Controller/API/LinkApiTest.php b/tests/Controller/API/LinkApiTest.php index 7d986fca8..b2508b602 100644 --- a/tests/Controller/API/LinkApiTest.php +++ b/tests/Controller/API/LinkApiTest.php @@ -448,6 +448,30 @@ public function test_update_request(): void ])->assertForbidden(); } + public function test_update_request_with_system_token(): void + { + $this->createSystemToken([ApiToken::ABILITY_LINKS_READ, ApiToken::ABILITY_LINKS_UPDATE]); + + $this->createTestLinks(); + $list = LinkList::factory()->create(); + + $this->assertDatabaseEmpty('link_lists'); + + $this->patchJsonAuthorized('api/v2/links/1', [ + 'url' => 'https://new-internal-link.com', + 'title' => 'Custom Title', + 'description' => 'Custom Description', + 'lists' => [$list->id], + 'visibility' => ModelAttribute::VISIBILITY_INTERNAL, + 'check_disabled' => false, + ], useSystemToken: true)->assertOk()->assertJson(['url' => 'https://new-internal-link.com']); + + $this->assertDatabaseHas('link_lists', [ + 'link_id' => 1, + 'list_id' => $list->id, + ]); + } + public function test_invalid_update_request(): void { Link::factory()->create(); @@ -491,6 +515,21 @@ public function test_delete_request(): void $this->assertEquals(2, Link::count()); } + public function test_delete_request_with_system_token(): void + { + $this->createSystemToken([ApiToken::ABILITY_LINKS_READ, ApiToken::ABILITY_LINKS_UPDATE, ApiToken::ABILITY_LINKS_DELETE]); + + $this->createTestLinks(); + + $this->assertEquals(3, Link::count()); + + $this->deleteJsonAuthorized('api/v2/links/1', useSystemToken: true)->assertOk(); + $this->deleteJsonAuthorized('api/v2/links/2', useSystemToken: true)->assertOk(); + $this->deleteJsonAuthorized('api/v2/links/3', useSystemToken: true)->assertForbidden(); // private cannot be deleted without proper ability + + $this->assertEquals(1, Link::count()); + } + public function test_delete_request_not_found(): void { $this->deleteJsonAuthorized('api/v2/links/1')->assertNotFound(); diff --git a/tests/Controller/API/ListApiTest.php b/tests/Controller/API/ListApiTest.php index e721d9432..f3e42ae49 100644 --- a/tests/Controller/API/ListApiTest.php +++ b/tests/Controller/API/ListApiTest.php @@ -2,6 +2,7 @@ namespace Tests\Controller\API; +use App\Enums\ApiToken; use App\Models\LinkList; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\Controller\Traits\PreparesTestData; @@ -178,4 +179,85 @@ public function test_delete_request_not_found(): void { $this->deleteJsonAuthorized('api/v2/lists/1')->assertNotFound(); } + + public function test_index_request_by_system_without_permission(): void + { + $this->createTestLists(); + $this->createSystemToken(); + + $this->getJsonAuthorized('api/v2/lists', useSystemToken: true) + ->assertForbidden(); + } + + public function test_index_request_by_system_with_permission(): void + { + $this->createTestLists(); + $this->createSystemToken([ApiToken::ABILITY_LISTS_READ]); + + $this->getJsonAuthorized('api/v2/lists', useSystemToken: true) + ->assertOk() + ->assertJson([ + 'data' => [ + ['name' => 'Internal List'], + ['name' => 'Public List'], + ], + ]) + ->assertJsonMissing([ + 'data' => [ + ['name' => 'Private List'], + ], + ]); + } + + public function test_update_request_by_system_without_permission(): void + { + $this->createTestLists(); + $list = LinkList::first(); + $this->createSystemToken(); + + $this->patchJsonAuthorized('api/v2/lists/' . $list->id, [ + 'name' => 'Updated List', + ], useSystemToken: true)->assertForbidden(); + } + + public function test_update_request_by_system_with_permission(): void + { + $this->createTestLists(); + $list = LinkList::first(); + $this->createSystemToken([ + ApiToken::ABILITY_LISTS_READ, + ApiToken::ABILITY_LISTS_UPDATE, + ]); + + $this->patchJsonAuthorized('api/v2/lists/' . $list->id, [ + 'name' => 'Updated List', + ], useSystemToken: true) + ->assertOk() + ->assertJson([ + 'name' => 'Updated List', + ]); + } + + public function test_delete_request_by_system_without_permission(): void + { + $this->createTestLists(); + $list = LinkList::first(); + $this->createSystemToken(); + + $this->deleteJsonAuthorized('api/v2/lists/' . $list->id, useSystemToken: true)->assertForbidden(); + } + + public function test_delete_request_by_system_with_permission(): void + { + $this->createTestLists(); + $list = LinkList::first(); + $this->createSystemToken([ + ApiToken::ABILITY_LISTS_READ, + ApiToken::ABILITY_LISTS_DELETE, + ]); + + $this->deleteJsonAuthorized('api/v2/lists/' . $list->id, useSystemToken: true)->assertOk(); + + $this->assertEquals(2, LinkList::count()); + } } diff --git a/tests/Controller/API/NoteApiTest.php b/tests/Controller/API/NoteApiTest.php index dfc7fd818..7d0348212 100644 --- a/tests/Controller/API/NoteApiTest.php +++ b/tests/Controller/API/NoteApiTest.php @@ -2,6 +2,7 @@ namespace Tests\Controller\API; +use App\Enums\ApiToken; use App\Models\Note; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\Controller\Traits\PreparesTestData; @@ -127,4 +128,55 @@ public function test_delete_request_not_found(): void { $this->deleteJsonAuthorized('api/v2/notes/1')->assertNotFound(); } + + public function test_update_request_by_system_without_permission(): void + { + $this->createTestLinks(); + $note = Note::factory()->create(['link_id' => 1, 'note' => 'Test Note', 'visibility' => 1]); + $this->createSystemToken(); + + $this->patchJsonAuthorized('api/v2/notes/' . $note->id, [ + 'note' => 'Updated Note', + ], useSystemToken: true)->assertForbidden(); + } + + public function test_update_request_by_system_with_permission(): void + { + $this->createTestLinks(); + $note = Note::factory()->create(['link_id' => 1, 'note' => 'Test Note', 'visibility' => 1]); + $this->createSystemToken([ + ApiToken::ABILITY_NOTES_READ, + ApiToken::ABILITY_NOTES_UPDATE, + ]); + + $this->patchJsonAuthorized('api/v2/notes/' . $note->id, [ + 'note' => 'Updated Note', + ], useSystemToken: true) + ->assertOk() + ->assertJson([ + 'note' => 'Updated Note', + ]); + } + + public function test_delete_request_by_system_without_permission(): void + { + $this->createTestLinks(); + $note = Note::factory()->create(['link_id' => 1, 'note' => 'Test Note', 'visibility' => 1]); + $this->createSystemToken(); + + $this->deleteJsonAuthorized('api/v2/notes/' . $note->id, useSystemToken: true)->assertForbidden(); + } + + public function test_delete_request_by_system_with_permission(): void + { + $note = Note::factory()->create(['link_id' => 1, 'note' => 'Test Note', 'visibility' => 1]); + $this->createSystemToken([ + ApiToken::ABILITY_NOTES_READ, + ApiToken::ABILITY_NOTES_DELETE, + ]); + + $this->deleteJsonAuthorized('api/v2/notes/' . $note->id, useSystemToken: true)->assertOk(); + + $this->assertEquals(0, Note::count()); + } } diff --git a/tests/Controller/API/TagApiTest.php b/tests/Controller/API/TagApiTest.php index 4cbd3c771..0daa44c02 100644 --- a/tests/Controller/API/TagApiTest.php +++ b/tests/Controller/API/TagApiTest.php @@ -2,6 +2,7 @@ namespace Tests\Controller\API; +use App\Enums\ApiToken; use App\Models\Tag; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\Controller\Traits\PreparesTestData; @@ -174,4 +175,85 @@ public function test_delete_request_not_found(): void { $this->deleteJsonAuthorized('api/v2/tags/1')->assertNotFound(); } + + public function test_index_request_by_system_without_permission(): void + { + $this->createTestTags(); + $this->createSystemToken(); + + $this->getJsonAuthorized('api/v2/tags', useSystemToken: true) + ->assertForbidden(); + } + + public function test_index_request_by_system_with_permission(): void + { + $this->createTestTags(); + $this->createSystemToken([ApiToken::ABILITY_TAGS_READ]); + + $this->getJsonAuthorized('api/v2/tags', useSystemToken: true) + ->assertOk() + ->assertJson([ + 'data' => [ + ['name' => 'Internal Tag'], + ['name' => 'Public Tag'], + ], + ]) + ->assertJsonMissing([ + 'data' => [ + ['name' => 'Private Tag'], + ], + ]); + } + + public function test_update_request_by_system_without_permission(): void + { + $this->createTestTags(); + $tag = Tag::first(); + $this->createSystemToken(); + + $this->patchJsonAuthorized('api/v2/tags/' . $tag->id, [ + 'name' => 'Updated Tag', + ], useSystemToken: true)->assertForbidden(); + } + + public function test_update_request_by_system_with_permission(): void + { + $this->createTestTags(); + $tag = Tag::first(); + $this->createSystemToken([ + ApiToken::ABILITY_TAGS_READ, + ApiToken::ABILITY_TAGS_UPDATE, + ]); + + $this->patchJsonAuthorized('api/v2/tags/' . $tag->id, [ + 'name' => 'Updated Tag', + ], useSystemToken: true) + ->assertOk() + ->assertJson([ + 'name' => 'Updated Tag', + ]); + } + + public function test_delete_request_by_system_without_permission(): void + { + $this->createTestTags(); + $tag = Tag::first(); + $this->createSystemToken(); + + $this->deleteJsonAuthorized('api/v2/tags/' . $tag->id, useSystemToken: true)->assertForbidden(); + } + + public function test_delete_request_by_system_with_permission(): void + { + $this->createTestTags(); + $tag = Tag::first(); + $this->createSystemToken([ + ApiToken::ABILITY_TAGS_READ, + ApiToken::ABILITY_TAGS_DELETE, + ]); + + $this->deleteJsonAuthorized('api/v2/tags/' . $tag->id, useSystemToken: true)->assertOk(); + + $this->assertEquals(2, Tag::count()); + } }