]> BookStack Code Mirror - bookstack/commitdiff
Applied another round of static analysis updates
authorDan Brown <redacted>
Mon, 22 Nov 2021 23:33:55 +0000 (23:33 +0000)
committerDan Brown <redacted>
Mon, 22 Nov 2021 23:33:55 +0000 (23:33 +0000)
21 files changed:
app/Actions/ActivityService.php
app/Auth/Access/LdapService.php
app/Auth/Access/Oidc/OidcJwtSigningKey.php
app/Auth/Access/SocialAuthService.php
app/Entities/Models/Book.php
app/Entities/Models/Bookshelf.php
app/Entities/Models/Chapter.php
app/Entities/Models/Entity.php
app/Entities/Models/PageRevision.php
app/Entities/Repos/PageRepo.php
app/Entities/Tools/PageContent.php
app/Entities/Tools/SearchIndex.php
app/Entities/Tools/SearchRunner.php
app/Entities/Tools/TrashCan.php
app/Http/Controllers/Api/BookshelfApiController.php
app/Http/Controllers/Api/ChapterApiController.php
app/Http/Controllers/BookController.php
app/Theming/ThemeService.php
app/Uploads/ImageRepo.php
tests/Unit/FrameworkAssumptionTest.php [new file with mode: 0644]
version

index 33ed44b32efa0dd64715af5a227e90a642cc0574..983c1a603c70b913d89a0392c74d31a11c438de2 100644 (file)
@@ -105,10 +105,10 @@ class ActivityService
         $queryIds = [$entity->getMorphClass() => [$entity->id]];
 
         if ($entity instanceof Book) {
-            $queryIds[(new Chapter())->getMorphClass()] = $entity->chapters()->visible()->pluck('id');
+            $queryIds[(new Chapter())->getMorphClass()] = $entity->chapters()->scopes('visible')->pluck('id');
         }
         if ($entity instanceof Book || $entity instanceof Chapter) {
-            $queryIds[(new Page())->getMorphClass()] = $entity->pages()->visible()->pluck('id');
+            $queryIds[(new Page())->getMorphClass()] = $entity->pages()->scopes('visible')->pluck('id');
         }
 
         $query = $this->activity->newQuery();
index e3a38537ac3ae4ae9def8d7336b29a330e587aa6..e529b80fdc5129ad326e82567ebb0b0a44a38404 100644 (file)
@@ -165,7 +165,7 @@ class LdapService
      * Bind the system user to the LDAP connection using the given credentials
      * otherwise anonymous access is attempted.
      *
-     * @param $connection
+     * @param resource $connection
      *
      * @throws LdapException
      */
index 9a5b3833ade3aec9e44faae9101703f81acb42e6..a70f3b3c74568ca8d05ae8d39d4d53246cb33b3a 100644 (file)
@@ -41,16 +41,18 @@ class OidcJwtSigningKey
     protected function loadFromPath(string $path)
     {
         try {
-            $this->key = PublicKeyLoader::load(
+            $key = PublicKeyLoader::load(
                 file_get_contents($path)
-            )->withPadding(RSA::SIGNATURE_PKCS1);
+            );
         } catch (\Exception $exception) {
             throw new OidcInvalidKeyException("Failed to load key from file path with error: {$exception->getMessage()}");
         }
 
-        if (!($this->key instanceof RSA)) {
+        if (!$key instanceof RSA) {
             throw new OidcInvalidKeyException('Key loaded from file path is not an RSA key as expected');
         }
+
+        $this->key = $key->withPadding(RSA::SIGNATURE_PKCS1);
     }
 
     /**
@@ -81,14 +83,19 @@ class OidcJwtSigningKey
         $n = strtr($jwk['n'] ?? '', '-_', '+/');
 
         try {
-            /** @var RSA $key */
-            $this->key = PublicKeyLoader::load([
+            $key = PublicKeyLoader::load([
                 'e' => new BigInteger(base64_decode($jwk['e']), 256),
                 'n' => new BigInteger(base64_decode($n), 256),
-            ])->withPadding(RSA::SIGNATURE_PKCS1);
+            ]);
         } catch (\Exception $exception) {
             throw new OidcInvalidKeyException("Failed to load key from JWK parameters with error: {$exception->getMessage()}");
         }
+
+        if (!$key instanceof RSA) {
+            throw new OidcInvalidKeyException('Key loaded from file path is not an RSA key as expected');
+        }
+
+        $this->key = $key->withPadding(RSA::SIGNATURE_PKCS1);
     }
 
     /**
index 23e95970cc3039e9313f6cabe924b1a6b24d0645..0df5ceb5ee679fe655c33a1fafd78f86226ab7af 100644 (file)
@@ -12,6 +12,7 @@ use Illuminate\Support\Str;
 use Laravel\Socialite\Contracts\Factory as Socialite;
 use Laravel\Socialite\Contracts\Provider;
 use Laravel\Socialite\Contracts\User as SocialUser;
+use Laravel\Socialite\Two\GoogleProvider;
 use SocialiteProviders\Manager\SocialiteWasCalled;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 
@@ -278,7 +279,7 @@ class SocialAuthService
     {
         $driver = $this->socialite->driver($driverName);
 
-        if ($driverName === 'google' && config('services.google.select_account')) {
+        if ($driver instanceof GoogleProvider && config('services.google.select_account')) {
             $driver->with(['prompt' => 'select_account']);
         }
 
index 735d25a991e0835ccd7a7c8dbe2b1453d7972ee3..8217d2cab90e48301433c20740ea7df6ede8a135 100644 (file)
@@ -79,53 +79,43 @@ class Book extends Entity implements HasCoverImage
 
     /**
      * Get all pages within this book.
-     *
-     * @return HasMany
      */
-    public function pages()
+    public function pages(): HasMany
     {
         return $this->hasMany(Page::class);
     }
 
     /**
      * Get the direct child pages of this book.
-     *
-     * @return HasMany
      */
-    public function directPages()
+    public function directPages(): HasMany
     {
         return $this->pages()->where('chapter_id', '=', '0');
     }
 
     /**
      * Get all chapters within this book.
-     *
-     * @return HasMany
      */
-    public function chapters()
+    public function chapters(): HasMany
     {
         return $this->hasMany(Chapter::class);
     }
 
     /**
      * Get the shelves this book is contained within.
-     *
-     * @return BelongsToMany
      */
-    public function shelves()
+    public function shelves(): BelongsToMany
     {
         return $this->belongsToMany(Bookshelf::class, 'bookshelves_books', 'book_id', 'bookshelf_id');
     }
 
     /**
      * Get the direct child items within this book.
-     *
-     * @return Collection
      */
     public function getDirectChildren(): Collection
     {
-        $pages = $this->directPages()->visible()->get();
-        $chapters = $this->chapters()->visible()->get();
+        $pages = $this->directPages()->scopes('visible')->get();
+        $chapters = $this->chapters()->scopes('visible')->get();
 
         return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft');
     }
index e4d9775b70b470813bd7562d49e04d87e2872934..b9ebab92ef88da243a9ef069f35cce0e5ddd0e14 100644 (file)
@@ -37,7 +37,7 @@ class Bookshelf extends Entity implements HasCoverImage
      */
     public function visibleBooks(): BelongsToMany
     {
-        return $this->books()->visible();
+        return $this->books()->scopes('visible');
     }
 
     /**
index 224ded935048ef86e581376ca1bd380c43a4787e..8fc2d333dbe43a99321414fb983b185070071c46 100644 (file)
@@ -23,6 +23,7 @@ class Chapter extends BookChild
 
     /**
      * Get the pages that this chapter contains.
+     * @return HasMany<Page>
      */
     public function pages(string $dir = 'ASC'): HasMany
     {
@@ -50,7 +51,8 @@ class Chapter extends BookChild
      */
     public function getVisiblePages(): Collection
     {
-        return $this->pages()->visible()
+        return $this->pages()
+        ->scopes('visible')
         ->orderBy('draft', 'desc')
         ->orderBy('priority', 'asc')
         ->get();
index 2b6f85d24ceff1f9b5b05954bbbe52c820bf8590..0eb402284498e568a521929d6c6c46eb1a9598b2 100644 (file)
@@ -121,11 +121,11 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
             return true;
         }
 
-        if (($entity->isA('chapter') || $entity->isA('page')) && $this->isA('book')) {
+        if (($entity instanceof BookChild) && $this instanceof Book) {
             return $entity->book_id === $this->id;
         }
 
-        if ($entity->isA('page') && $this->isA('chapter')) {
+        if ($entity instanceof Page && $this instanceof Chapter) {
             return $entity->chapter_id === $this->id;
         }
 
index a3c4379a6a68bb3e0cc2af94a02c354f94bcee33..2bfa169f476c3f67fd88a4d2539862aa7a955143 100644 (file)
@@ -63,10 +63,8 @@ class PageRevision extends Model
 
     /**
      * Get the previous revision for the same page if existing.
-     *
-     * @return \BookStack\Entities\PageRevision|null
      */
-    public function getPrevious()
+    public function getPrevious(): ?PageRevision
     {
         $id = static::newQuery()->where('page_id', '=', $this->page_id)
             ->where('id', '<', $this->id)
index ed5534f082073677415db00b6a225f87b76bbe57..24fc1e7ddd2a20d5fcf6807265d11f62c925b866 100644 (file)
@@ -69,9 +69,10 @@ class PageRepo
      */
     public function getByOldSlug(string $bookSlug, string $pageSlug): ?Page
     {
+        /** @var ?PageRevision $revision */
         $revision = PageRevision::query()
             ->whereHas('page', function (Builder $query) {
-                $query->visible();
+                $query->scopes('visible');
             })
             ->where('slug', '=', $pageSlug)
             ->where('type', '=', 'version')
@@ -80,7 +81,7 @@ class PageRepo
             ->with('page')
             ->first();
 
-        return $revision ? $revision->page : null;
+        return $revision->page ?? null;
     }
 
     /**
index c60cf03113a9cceaa980f040325b5df8cacd0e8d..b95131fce2e25561c22131d3d81e3dc4d30a8aac 100644 (file)
@@ -12,6 +12,8 @@ use BookStack\Uploads\ImageRepo;
 use BookStack\Uploads\ImageService;
 use BookStack\Util\HtmlContentFilter;
 use DOMDocument;
+use DOMElement;
+use DOMNode;
 use DOMNodeList;
 use DOMXPath;
 use Illuminate\Support\Str;
@@ -237,9 +239,9 @@ class PageContent
      * A map for existing ID's should be passed in to check for current existence.
      * Returns a pair of strings in the format [old_id, new_id].
      */
-    protected function setUniqueId(\DOMNode $element, array &$idMap): array
+    protected function setUniqueId(DOMNode $element, array &$idMap): array
     {
-        if (!$element instanceof \DOMElement) {
+        if (!$element instanceof DOMElement) {
             return ['', ''];
         }
 
@@ -321,7 +323,7 @@ class PageContent
      */
     protected function headerNodesToLevelList(DOMNodeList $nodeList): array
     {
-        $tree = collect($nodeList)->map(function ($header) {
+        $tree = collect($nodeList)->map(function (DOMElement $header) {
             $text = trim(str_replace("\xc2\xa0", '', $header->nodeValue));
             $text = mb_substr($text, 0, 100);
 
index 79139ec24062433ed136bebd77cd57fd130bfd30..d43d982079d88262280dbe9c70b40d11632d0577 100644 (file)
@@ -9,6 +9,7 @@ use BookStack\Entities\Models\Page;
 use BookStack\Entities\Models\SearchTerm;
 use DOMDocument;
 use DOMNode;
+use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Support\Collection;
 
 class SearchIndex
@@ -76,7 +77,9 @@ class SearchIndex
         foreach ($this->entityProvider->all() as $entityModel) {
             $indexContentField = $entityModel instanceof Page ? 'html' : 'description';
             $selectFields = ['id', 'name', $indexContentField];
-            $total = $entityModel->newQuery()->withTrashed()->count();
+            /** @var Builder<Entity> $query */
+            $query = $entityModel->newQuery();
+            $total = $query->withTrashed()->count();
             $chunkSize = 250;
             $processed = 0;
 
@@ -223,7 +226,7 @@ class SearchIndex
         if ($entity instanceof Page) {
             $bodyTermsMap = $this->generateTermScoreMapFromHtml($entity->html);
         } else {
-            $bodyTermsMap = $this->generateTermScoreMapFromText($entity->description ?? '', $entity->searchFactor);
+            $bodyTermsMap = $this->generateTermScoreMapFromText($entity->getAttribute('description') ?? '', $entity->searchFactor);
         }
 
         $mergedScoreMap = $this->mergeTermScoreMaps($nameTermsMap, $bodyTermsMap, $tagTermsMap);
index a4f1314820d1d39abc9f62d20f715a67901845d4..a0a44f3a553b0bf1791afc08645da769e59e70d8 100644 (file)
@@ -145,13 +145,13 @@ class SearchRunner
 
         if ($entityModelInstance instanceof BookChild) {
             $relations['book'] = function (BelongsTo $query) {
-                $query->visible();
+                $query->scopes('visible');
             };
         }
 
         if ($entityModelInstance instanceof Page) {
             $relations['chapter'] = function (BelongsTo $query) {
-                $query->visible();
+                $query->scopes('visible');
             };
         }
 
index fcf933726d2e0b6d8320ef0e9b72ab07b989dbc3..ab62165af1583e146298652e60a984571e12f9ff 100644 (file)
@@ -15,6 +15,7 @@ use BookStack\Facades\Activity;
 use BookStack\Uploads\AttachmentService;
 use BookStack\Uploads\ImageService;
 use Exception;
+use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Support\Carbon;
 
 class TrashCan
@@ -141,11 +142,9 @@ class TrashCan
     {
         $count = 0;
         $pages = $chapter->pages()->withTrashed()->get();
-        if (count($pages)) {
-            foreach ($pages as $page) {
-                $this->destroyPage($page);
-                $count++;
-            }
+        foreach ($pages as $page) {
+            $this->destroyPage($page);
+            $count++;
         }
 
         $this->destroyCommonRelations($chapter);
@@ -183,9 +182,10 @@ class TrashCan
     {
         $counts = [];
 
-        /** @var Entity $instance */
         foreach ((new EntityProvider())->all() as $key => $instance) {
-            $counts[$key] = $instance->newQuery()->onlyTrashed()->count();
+            /** @var Builder<Entity> $query */
+            $query = $instance->newQuery();
+            $counts[$key] = $query->onlyTrashed()->count();
         }
 
         return $counts;
@@ -344,9 +344,9 @@ class TrashCan
         $entity->deletions()->delete();
         $entity->favourites()->delete();
 
-        if ($entity instanceof HasCoverImage && $entity->cover) {
+        if ($entity instanceof HasCoverImage && $entity->cover()->exists()) {
             $imageService = app()->make(ImageService::class);
-            $imageService->destroy($entity->cover);
+            $imageService->destroy($entity->cover()->first());
         }
     }
 }
index de7284e61562c14391b3658c3f96dc950e42f13d..bd4f23a1093257cecd971a030d5c48109fc3a313 100644 (file)
@@ -75,7 +75,7 @@ class BookshelfApiController extends ApiController
         $shelf = Bookshelf::visible()->with([
             'tags', 'cover', 'createdBy', 'updatedBy', 'ownedBy',
             'books' => function (BelongsToMany $query) {
-                $query->visible()->get(['id', 'name', 'slug']);
+                $query->scopes('visible')->get(['id', 'name', 'slug']);
             },
         ])->findOrFail($id);
 
index 6b226b5f0fcf57f6dcf0cb9d1254d4e5c73e8b5c..8459b84499ee362919e87833ad7527b2a7e33928 100644 (file)
@@ -70,7 +70,7 @@ class ChapterApiController extends ApiController
     public function read(string $id)
     {
         $chapter = Chapter::visible()->with(['tags', 'createdBy', 'updatedBy', 'ownedBy', 'pages' => function (HasMany $query) {
-            $query->visible()->get(['id', 'name', 'slug']);
+            $query->scopes('visible')->get(['id', 'name', 'slug']);
         }])->findOrFail($id);
 
         return response()->json($chapter);
index af44a668978d73b535978be0690b36ec998c51cd..51cba642c8e6319663b707bd106bad09ddaa9754 100644 (file)
@@ -114,7 +114,7 @@ class BookController extends Controller
     {
         $book = $this->bookRepo->getBySlug($slug);
         $bookChildren = (new BookContents($book))->getTree(true);
-        $bookParentShelves = $book->shelves()->visible()->get();
+        $bookParentShelves = $book->shelves()->scopes('visible')->get();
 
         View::incrementFor($book);
         if ($request->has('shelf')) {
index 7bc12c5d1561f5e1463b8ff52dea935b7b78e0e4..57336ec5f71d21a3156aa69120fb194d76104a2e 100644 (file)
@@ -52,7 +52,7 @@ class ThemeService
      */
     public function registerCommand(Command $command)
     {
-        Artisan::starting(function(Application $application) use ($command) {
+        Artisan::starting(function (Application $application) use ($command) {
             $application->addCommands([$command]);
         });
     }
index 494ff3ac0d02c703d06be0a8c58785b3132e0520..bfe4b597739a8e26f423c4f5f04d9d78f0db805f 100644 (file)
@@ -103,7 +103,10 @@ class ImageRepo
                 if ($filterType === 'page') {
                     $query->where('uploaded_to', '=', $contextPage->id);
                 } elseif ($filterType === 'book') {
-                    $validPageIds = $contextPage->book->pages()->visible()->pluck('id')->toArray();
+                    $validPageIds = $contextPage->book->pages()
+                        ->scopes('visible')
+                        ->pluck('id')
+                        ->toArray();
                     $query->whereIn('uploaded_to', $validPageIds);
                 }
             };
diff --git a/tests/Unit/FrameworkAssumptionTest.php b/tests/Unit/FrameworkAssumptionTest.php
new file mode 100644 (file)
index 0000000..8ad4561
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace Tests\Unit;
+
+use BadMethodCallException;
+use BookStack\Entities\Models\Page;
+use Tests\TestCase;
+
+/**
+ * This class tests assumptions we're relying upon in the framework.
+ * This is primarily to keep track of certain bits of functionality that
+ * may be used in important areas such as to enforce permissions.
+ */
+class FrameworkAssumptionTest extends TestCase
+{
+
+    public function test_scopes_error_if_not_existing()
+    {
+        $this->expectException(BadMethodCallException::class);
+        $this->expectExceptionMessage('Call to undefined method BookStack\Entities\Models\Page::scopeNotfoundscope()');
+        Page::query()->scopes('notfoundscope');
+    }
+
+    public function test_scopes_applies_upon_existing()
+    {
+        // Page has SoftDeletes trait by default, so we apply our custom scope and ensure
+        // it stacks on the global scope to filter out deleted items.
+        $query = Page::query()->scopes('visible')->toSql();
+        $this->assertStringContainsString('joint_permissions', $query);
+        $this->assertStringContainsString('`deleted_at` is null', $query);
+    }
+}
diff --git a/version b/version
index 4a22367ef577fc75cdf9c5788579851707d70425..8f6af9beed9605ee6117c937ed602cbefb4f2ff2 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-v21.10-dev
+v21.11-dev
Morty Proxy This is a proxified and sanitized view of the page, visit original site.