From 8901073d126ed86b535fe910a437b2170bfc14ca Mon Sep 17 00:00:00 2001 From: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com> Date: Mon, 7 Dec 2020 20:02:46 -0500 Subject: [PATCH] Model Visibility Scoping Extender and Tests (#2460) --- src/Database/ScopeVisibilityTrait.php | 38 +++- .../Access/ScopeDiscussionVisibility.php | 61 ++++++ src/Discussion/DiscussionPolicy.php | 53 ----- src/Discussion/DiscussionServiceProvider.php | 3 + src/Event/ScopeModelVisibility.php | 2 + src/Extend/ModelVisibility.php | 102 ++++++++++ src/Group/Access/ScopeGroupVisibility.php | 27 +++ src/Group/GroupPolicy.php | 12 -- src/Group/GroupServiceProvider.php | 3 + src/Notification/Notification.php | 7 +- src/Post/Access/ScopePostVisibility.php | 62 ++++++ src/Post/PostPolicy.php | 55 ----- src/Post/PostServiceProvider.php | 3 + src/User/AbstractPolicy.php | 1 + src/User/Access/ScopeUserVisibility.php | 31 +++ src/User/UserPolicy.php | 17 -- src/User/UserServiceProvider.php | 3 + .../extenders/ModelVisibilityTest.php | 189 ++++++++++++++++++ tests/integration/extenders/UserTest.php | 3 + 19 files changed, 527 insertions(+), 145 deletions(-) create mode 100644 src/Discussion/Access/ScopeDiscussionVisibility.php create mode 100644 src/Extend/ModelVisibility.php create mode 100644 src/Group/Access/ScopeGroupVisibility.php create mode 100644 src/Post/Access/ScopePostVisibility.php create mode 100644 src/User/Access/ScopeUserVisibility.php create mode 100644 tests/integration/extenders/ModelVisibilityTest.php diff --git a/src/Database/ScopeVisibilityTrait.php b/src/Database/ScopeVisibilityTrait.php index e583a4ec8f..2931c7fb6d 100644 --- a/src/Database/ScopeVisibilityTrait.php +++ b/src/Database/ScopeVisibilityTrait.php @@ -12,19 +12,49 @@ use Flarum\Event\ScopeModelVisibility; use Flarum\User\User; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Arr; trait ScopeVisibilityTrait { + protected static $visibilityScopers = []; + + public static function registerVisibilityScoper($scoper, $ability = null) + { + $model = static::class; + + if ($ability === null) { + $ability = '*'; + } + + if (! Arr::has(static::$visibilityScopers, "$model.$ability")) { + Arr::set(static::$visibilityScopers, "$model.$ability", []); + } + + static::$visibilityScopers[$model][$ability][] = $scoper; + } + /** * Scope a query to only include records that are visible to a user. * * @param Builder $query * @param User $actor */ - public function scopeWhereVisibleTo(Builder $query, User $actor) + public function scopeWhereVisibleTo(Builder $query, User $actor, string $ability = 'view') { - static::$dispatcher->dispatch( - new ScopeModelVisibility($query, $actor, 'view') - ); + /** + * @deprecated beta 15, remove beta 15 + */ + static::$dispatcher->dispatch(new ScopeModelVisibility($query, $actor, $ability)); + + foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) { + foreach (Arr::get(static::$visibilityScopers, "$class.*", []) as $listener) { + $listener($actor, $query, $ability); + } + foreach (Arr::get(static::$visibilityScopers, "$class.$ability", []) as $listener) { + $listener($actor, $query); + } + } + + return $query; } } diff --git a/src/Discussion/Access/ScopeDiscussionVisibility.php b/src/Discussion/Access/ScopeDiscussionVisibility.php new file mode 100644 index 0000000000..ca92cf9311 --- /dev/null +++ b/src/Discussion/Access/ScopeDiscussionVisibility.php @@ -0,0 +1,61 @@ +cannot('viewDiscussions')) { + $query->whereRaw('FALSE'); + + return; + } + + // Hide private discussions by default. + $query->where(function ($query) use ($actor) { + $query->where('discussions.is_private', false) + ->orWhere(function ($query) use ($actor) { + $query->whereVisibleTo($actor, 'viewPrivate'); + }); + }); + + // Hide hidden discussions, unless they are authored by the current + // user, or the current user has permission to view hidden discussions. + if (! $actor->hasPermission('discussion.hide')) { + $query->where(function ($query) use ($actor) { + $query->whereNull('discussions.hidden_at') + ->orWhere('discussions.user_id', $actor->id) + ->orWhere(function ($query) use ($actor) { + $query->whereVisibleTo($actor, 'hide'); + }); + }); + } + + // Hide discussions with no comments, unless they are authored by the + // current user, or the user is allowed to edit the discussion's posts. + if (! $actor->hasPermission('discussion.editPosts')) { + $query->where(function ($query) use ($actor) { + $query->where('discussions.comment_count', '>', 0) + ->orWhere('discussions.user_id', $actor->id) + ->orWhere(function ($query) use ($actor) { + $query->whereVisibleTo($actor, 'editPosts'); + }); + }); + } + } +} diff --git a/src/Discussion/DiscussionPolicy.php b/src/Discussion/DiscussionPolicy.php index 4d353939f3..183626f371 100644 --- a/src/Discussion/DiscussionPolicy.php +++ b/src/Discussion/DiscussionPolicy.php @@ -9,12 +9,10 @@ namespace Flarum\Discussion; -use Flarum\Event\ScopeModelVisibility; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\User\AbstractPolicy; use Flarum\User\User; use Illuminate\Contracts\Events\Dispatcher; -use Illuminate\Database\Eloquent\Builder; class DiscussionPolicy extends AbstractPolicy { @@ -55,57 +53,6 @@ public function can(User $actor, $ability) } } - /** - * @param User $actor - * @param Builder $query - */ - public function find(User $actor, Builder $query) - { - if ($actor->cannot('viewDiscussions')) { - $query->whereRaw('FALSE'); - - return; - } - - // Hide private discussions by default. - $query->where(function ($query) use ($actor) { - $query->where('discussions.is_private', false) - ->orWhere(function ($query) use ($actor) { - $this->events->dispatch( - new ScopeModelVisibility($query, $actor, 'viewPrivate') - ); - }); - }); - - // Hide hidden discussions, unless they are authored by the current - // user, or the current user has permission to view hidden discussions. - if (! $actor->hasPermission('discussion.hide')) { - $query->where(function ($query) use ($actor) { - $query->whereNull('discussions.hidden_at') - ->orWhere('discussions.user_id', $actor->id) - ->orWhere(function ($query) use ($actor) { - $this->events->dispatch( - new ScopeModelVisibility($query, $actor, 'hide') - ); - }); - }); - } - - // Hide discussions with no comments, unless they are authored by the - // current user, or the user is allowed to edit the discussion's posts. - if (! $actor->hasPermission('discussion.editPosts')) { - $query->where(function ($query) use ($actor) { - $query->where('discussions.comment_count', '>', 0) - ->orWhere('discussions.user_id', $actor->id) - ->orWhere(function ($query) use ($actor) { - $this->events->dispatch( - new ScopeModelVisibility($query, $actor, 'editPosts') - ); - }); - }); - } - } - /** * @param User $actor * @param \Flarum\Discussion\Discussion $discussion diff --git a/src/Discussion/DiscussionServiceProvider.php b/src/Discussion/DiscussionServiceProvider.php index 3f0ba99977..c5f1cc383e 100644 --- a/src/Discussion/DiscussionServiceProvider.php +++ b/src/Discussion/DiscussionServiceProvider.php @@ -9,6 +9,7 @@ namespace Flarum\Discussion; +use Flarum\Discussion\Access\ScopeDiscussionVisibility; use Flarum\Discussion\Event\Renamed; use Flarum\Foundation\AbstractServiceProvider; @@ -28,5 +29,7 @@ public function boot() Renamed::class, DiscussionRenamedLogger::class ); + + Discussion::registerVisibilityScoper(new ScopeDiscussionVisibility(), 'view'); } } diff --git a/src/Event/ScopeModelVisibility.php b/src/Event/ScopeModelVisibility.php index 56c39ffc99..f0153c5502 100644 --- a/src/Event/ScopeModelVisibility.php +++ b/src/Event/ScopeModelVisibility.php @@ -15,6 +15,8 @@ /** * The `ScopeModelVisibility` event allows constraints to be applied in a query * to fetch a model, effectively scoping that model's visibility to the user. + * + * @deprecated beta 15, remove beta 16 */ class ScopeModelVisibility { diff --git a/src/Extend/ModelVisibility.php b/src/Extend/ModelVisibility.php new file mode 100644 index 0000000000..92e81e6d1b --- /dev/null +++ b/src/Extend/ModelVisibility.php @@ -0,0 +1,102 @@ +modelClass = $modelClass; + + if (! method_exists($modelClass, 'registerVisibilityScoper')) { + throw new Exception("Model $modelClass cannot be visibility scoped as it does not use Flarum\Database\ScopeVisibilityTrait."); + } + } + + /** + * Add a scoper for a given ability. + * + * @param callable|string $callback + * @param string $ability, defaults to 'view' + * + * The callback can be a closure or invokable class, and should accept: + * - \Flarum\User\User $actor + * - \Illuminate\Database\Eloquent\Builder $query + * + * @return self + */ + public function scope($callback, $ability = 'view') + { + $this->scopers[$ability][] = $callback; + + return $this; + } + + /** + * Add a scoper scoper that will always run for this model, regardless of requested ability. + * + * @param callable|string $callback + * + * The callback can be a closure or invokable class, and should accept: + * - \Flarum\User\User $actor + * - \Illuminate\Database\Eloquent\Builder $query + * - string $ability + * + * @return self + */ + public function scopeAll($callback) + { + $this->allScopers[] = $callback; + + return $this; + } + + public function extend(Container $container, Extension $extension = null) + { + foreach ($this->scopers as $ability => $scopers) { + foreach ($scopers as $scoper) { + $this->modelClass::registerVisibilityScoper(ContainerUtil::wrapCallback($scoper, $container), $ability); + } + } + + foreach ($this->allScopers as $scoper) { + $this->modelClass::registerVisibilityScoper(ContainerUtil::wrapCallback($scoper, $container)); + } + } +} diff --git a/src/Group/Access/ScopeGroupVisibility.php b/src/Group/Access/ScopeGroupVisibility.php new file mode 100644 index 0000000000..88f5a11abd --- /dev/null +++ b/src/Group/Access/ScopeGroupVisibility.php @@ -0,0 +1,27 @@ +cannot('viewHiddenGroups')) { + $query->where('is_hidden', false); + } + } +} diff --git a/src/Group/GroupPolicy.php b/src/Group/GroupPolicy.php index 232d8bb0d7..e3f6d08747 100644 --- a/src/Group/GroupPolicy.php +++ b/src/Group/GroupPolicy.php @@ -11,7 +11,6 @@ use Flarum\User\AbstractPolicy; use Flarum\User\User; -use Illuminate\Database\Eloquent\Builder; class GroupPolicy extends AbstractPolicy { @@ -31,15 +30,4 @@ public function can(User $actor, $ability) return true; } } - - /** - * @param User $actor - * @param Builder $query - */ - public function find(User $actor, Builder $query) - { - if ($actor->cannot('viewHiddenGroups')) { - $query->where('is_hidden', false); - } - } } diff --git a/src/Group/GroupServiceProvider.php b/src/Group/GroupServiceProvider.php index dea6a7b6b5..0a02346950 100644 --- a/src/Group/GroupServiceProvider.php +++ b/src/Group/GroupServiceProvider.php @@ -10,6 +10,7 @@ namespace Flarum\Group; use Flarum\Foundation\AbstractServiceProvider; +use Flarum\Group\Access\ScopeGroupVisibility; class GroupServiceProvider extends AbstractServiceProvider { @@ -20,5 +21,7 @@ public function boot() { $events = $this->app->make('events'); $events->subscribe(GroupPolicy::class); + + Group::registerVisibilityScoper(new ScopeGroupVisibility(), 'view'); } } diff --git a/src/Notification/Notification.php b/src/Notification/Notification.php index 94782dddf9..e48ad08ba1 100644 --- a/src/Notification/Notification.php +++ b/src/Notification/Notification.php @@ -11,7 +11,6 @@ use Carbon\Carbon; use Flarum\Database\AbstractModel; -use Flarum\Event\ScopeModelVisibility; use Flarum\Notification\Blueprint\BlueprintInterface; use Flarum\User\User; use Illuminate\Database\Eloquent\Builder; @@ -161,9 +160,9 @@ public function scopeWhereSubjectVisibleTo(Builder $query, User $actor) ->from((new $class)->getTable()) ->whereColumn('id', 'subject_id'); - static::$dispatcher->dispatch( - new ScopeModelVisibility($class::query()->setQuery($query), $actor, 'view') - ); + if (method_exists($class, 'registerVisibilityScoper')) { + $class::query()->setQuery($query)->whereVisibleTo($actor); + } }); }); } diff --git a/src/Post/Access/ScopePostVisibility.php b/src/Post/Access/ScopePostVisibility.php new file mode 100644 index 0000000000..c0e7390767 --- /dev/null +++ b/src/Post/Access/ScopePostVisibility.php @@ -0,0 +1,62 @@ +whereExists(function ($query) use ($actor) { + $query->selectRaw('1') + ->from('discussions') + ->whereColumn('discussions.id', 'posts.discussion_id'); + Discussion::query()->setQuery($query)->whereVisibleTo($actor); + }); + + // Hide private posts by default. + $query->where(function ($query) use ($actor) { + $query->where('posts.is_private', false) + ->orWhere(function ($query) use ($actor) { + $query->whereVisibleTo($actor, 'viewPrivate'); + }); + }); + + // Hide hidden posts, unless they are authored by the current user, or + // the current user has permission to view hidden posts in the + // discussion. + if (! $actor->hasPermission('discussion.hidePosts')) { + $query->where(function ($query) use ($actor) { + $query->whereNull('posts.hidden_at') + ->orWhere('posts.user_id', $actor->id) + ->orWhereExists(function ($query) use ($actor) { + $query->selectRaw('1') + ->from('discussions') + ->whereColumn('discussions.id', 'posts.discussion_id') + ->where(function ($query) use ($actor) { + $query + ->whereRaw('1=0') + ->orWhere(function ($query) use ($actor) { + Discussion::query()->setQuery($query)->whereVisibleTo($actor, 'hidePosts'); + }); + }); + }); + }); + } + } +} diff --git a/src/Post/PostPolicy.php b/src/Post/PostPolicy.php index cdadfddc40..7f27b31734 100644 --- a/src/Post/PostPolicy.php +++ b/src/Post/PostPolicy.php @@ -10,13 +10,10 @@ namespace Flarum\Post; use Carbon\Carbon; -use Flarum\Discussion\Discussion; -use Flarum\Event\ScopeModelVisibility; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\User\AbstractPolicy; use Flarum\User\User; use Illuminate\Contracts\Events\Dispatcher; -use Illuminate\Database\Eloquent\Builder; class PostPolicy extends AbstractPolicy { @@ -58,58 +55,6 @@ public function can(User $actor, $ability, Post $post) } } - /** - * @param User $actor - * @param Builder $query - */ - public function find(User $actor, $query) - { - // Make sure the post's discussion is visible as well. - $query->whereExists(function ($query) use ($actor) { - $query->selectRaw('1') - ->from('discussions') - ->whereColumn('discussions.id', 'posts.discussion_id'); - - $this->events->dispatch( - new ScopeModelVisibility(Discussion::query()->setQuery($query), $actor, 'view') - ); - }); - - // Hide private posts by default. - $query->where(function ($query) use ($actor) { - $query->where('posts.is_private', false) - ->orWhere(function ($query) use ($actor) { - $this->events->dispatch( - new ScopeModelVisibility($query, $actor, 'viewPrivate') - ); - }); - }); - - // Hide hidden posts, unless they are authored by the current user, or - // the current user has permission to view hidden posts in the - // discussion. - if (! $actor->hasPermission('discussion.hidePosts')) { - $query->where(function ($query) use ($actor) { - $query->whereNull('posts.hidden_at') - ->orWhere('posts.user_id', $actor->id) - ->orWhereExists(function ($query) use ($actor) { - $query->selectRaw('1') - ->from('discussions') - ->whereColumn('discussions.id', 'posts.discussion_id') - ->where(function ($query) use ($actor) { - $query - ->whereRaw('1=0') - ->orWhere(function ($query) use ($actor) { - $this->events->dispatch( - new ScopeModelVisibility(Discussion::query()->setQuery($query), $actor, 'hidePosts') - ); - }); - }); - }); - }); - } - } - /** * @param User $actor * @param Post $post diff --git a/src/Post/PostServiceProvider.php b/src/Post/PostServiceProvider.php index 37308d558b..9168c4f614 100644 --- a/src/Post/PostServiceProvider.php +++ b/src/Post/PostServiceProvider.php @@ -12,6 +12,7 @@ use DateTime; use Flarum\Event\ConfigurePostTypes; use Flarum\Foundation\AbstractServiceProvider; +use Flarum\Post\Access\ScopePostVisibility; class PostServiceProvider extends AbstractServiceProvider { @@ -52,6 +53,8 @@ public function boot() $events = $this->app->make('events'); $events->subscribe(PostPolicy::class); + + Post::registerVisibilityScoper(new ScopePostVisibility(), 'view'); } protected function setPostTypes() diff --git a/src/User/AbstractPolicy.php b/src/User/AbstractPolicy.php index 451fca6995..ed0f517529 100644 --- a/src/User/AbstractPolicy.php +++ b/src/User/AbstractPolicy.php @@ -54,6 +54,7 @@ public function getPermission(GetPermission $event) /** * @param ScopeModelVisibility $event + * @deprecated beta 15, remove beta 16 */ public function scopeModelVisibility(ScopeModelVisibility $event) { diff --git a/src/User/Access/ScopeUserVisibility.php b/src/User/Access/ScopeUserVisibility.php new file mode 100644 index 0000000000..70e3fbe2ea --- /dev/null +++ b/src/User/Access/ScopeUserVisibility.php @@ -0,0 +1,31 @@ +cannot('viewDiscussions')) { + if ($actor->isGuest()) { + $query->whereRaw('FALSE'); + } else { + $query->where('id', $actor->id); + } + } + } +} diff --git a/src/User/UserPolicy.php b/src/User/UserPolicy.php index 4ccf128da5..95bb98e39c 100644 --- a/src/User/UserPolicy.php +++ b/src/User/UserPolicy.php @@ -9,8 +9,6 @@ namespace Flarum\User; -use Illuminate\Database\Eloquent\Builder; - class UserPolicy extends AbstractPolicy { /** @@ -29,19 +27,4 @@ public function can(User $actor, $ability) return true; } } - - /** - * @param User $actor - * @param Builder $query - */ - public function find(User $actor, Builder $query) - { - if ($actor->cannot('viewDiscussions')) { - if ($actor->isGuest()) { - $query->whereRaw('FALSE'); - } else { - $query->where('id', $actor->id); - } - } - } } diff --git a/src/User/UserServiceProvider.php b/src/User/UserServiceProvider.php index fa7968723e..bea62a4b99 100644 --- a/src/User/UserServiceProvider.php +++ b/src/User/UserServiceProvider.php @@ -12,6 +12,7 @@ use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\ContainerUtil; use Flarum\Settings\SettingsRepositoryInterface; +use Flarum\User\Access\ScopeUserVisibility; use Flarum\User\DisplayName\DriverInterface; use Flarum\User\DisplayName\UsernameDriver; use Flarum\User\Event\EmailChangeRequested; @@ -96,5 +97,7 @@ public function boot() User::registerPreference('discloseOnline', 'boolval', true); User::registerPreference('indexProfile', 'boolval', true); User::registerPreference('locale'); + + User::registerVisibilityScoper(new ScopeUserVisibility(), 'view'); } } diff --git a/tests/integration/extenders/ModelVisibilityTest.php b/tests/integration/extenders/ModelVisibilityTest.php new file mode 100644 index 0000000000..0121e6e8a1 --- /dev/null +++ b/tests/integration/extenders/ModelVisibilityTest.php @@ -0,0 +1,189 @@ +prepareDatabase([ + 'discussions' => [ + ['id' => 1, 'title' => 'Empty discussion', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => null, 'comment_count' => 0, 'is_private' => 0], + ['id' => 2, 'title' => 'Discussion with post', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 1, 'comment_count' => 1, 'is_private' => 0], + ['id' => 3, 'title' => 'Private discussion', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'first_post_id' => 2, 'comment_count' => 1, 'is_private' => 1], + ], + 'posts' => [ + ['id' => 1, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => 'a normal reply - too-obscure'], + ['id' => 2, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => 'private!'], + ], + 'users' => [ + $this->normalUser(), + ], + 'groups' => [ + $this->guestGroup(), + $this->memberGroup(), + ], + 'group_user' => [ + ['user_id' => 2, 'group_id' => 3], + ], + 'group_permission' => [ + ['permission' => 'viewDiscussions', 'group_id' => 2], + ['permission' => 'viewDiscussions', 'group_id' => 3], + ] + ]); + } + + /** + * @test + */ + public function user_can_see_posts_by_default() + { + $this->prepDb(); + + $actor = User::find(2); + + $visiblePosts = CommentPost::query()->whereVisibleTo($actor)->get(); + + $this->assertCount(1, $visiblePosts); + } + + /** + * @test + */ + public function custom_visibility_scoper_can_stop_user_from_seeing_posts() + { + $this->extend( + (new Extend\ModelVisibility(CommentPost::class)) + ->scope(function (User $user, Builder $query) { + $query->whereRaw('1=0'); + }, 'view') + ); + + $this->prepDb(); + + $actor = User::find(2); + + $visiblePosts = CommentPost::query()->whereVisibleTo($actor)->get(); + + $this->assertCount(0, $visiblePosts); + } + + /** + * @test + */ + public function custom_visibility_scoper_applies_if_added_to_parent_class() + { + $this->extend( + (new Extend\ModelVisibility(Post::class)) + ->scope(function (User $user, Builder $query) { + $query->whereRaw('1=0'); + }, 'view') + ); + + $this->prepDb(); + + $actor = User::find(2); + + $visiblePosts = CommentPost::query()->whereVisibleTo($actor)->get(); + + $this->assertCount(0, $visiblePosts); + } + + /** + * @test + */ + public function custom_visibility_scoper_for_class_applied_after_scopers_for_parent_class() + { + $this->extend( + (new Extend\ModelVisibility(CommentPost::class)) + ->scope(function (User $user, Builder $query) { + $query->orWhereRaw('1=1'); + }, 'view'), + (new Extend\ModelVisibility(Post::class)) + ->scope(function (User $user, Builder $query) { + $query->whereRaw('1=0'); + }, 'view') + ); + + $this->prepDb(); + + $actor = User::find(2); + + $visiblePosts = CommentPost::query()->whereVisibleTo($actor)->get(); + + $this->assertCount(2, $visiblePosts); + } + + /** + * @test + */ + public function custom_scoper_works_for_abilities_other_than_view() + { + $this->extend( + (new Extend\ModelVisibility(Discussion::class)) + ->scope(function (User $user, Builder $query) { + $query->whereRaw('1=1'); + }, 'viewPrivate'), + (new Extend\ModelVisibility(Post::class)) + ->scope(function (User $user, Builder $query) { + $query->whereRaw('1=1'); + }, 'viewPrivate') + ); + + $this->prepDb(); + + $actor = User::find(2); + + $visiblePosts = CommentPost::query()->whereVisibleTo($actor)->get(); + + $this->assertCount(2, $visiblePosts); + } + + /** + * @test + */ + public function universal_scoper_works() + { + $this->extend( + (new Extend\ModelVisibility(Discussion::class)) + ->scopeAll(function (User $user, Builder $query, string $ability) { + if ($ability == 'viewPrivate') { + $query->whereRaw('1=1'); + } + }), + (new Extend\ModelVisibility(Post::class)) + ->scopeAll(function (User $user, Builder $query, string $ability) { + if ($ability == 'viewPrivate') { + $query->whereRaw('1=1'); + } + }) + ); + + $this->prepDb(); + + $actor = User::find(2); + + $visiblePosts = CommentPost::query()->whereVisibleTo($actor)->get(); + + $this->assertCount(2, $visiblePosts); + } +} diff --git a/tests/integration/extenders/UserTest.php b/tests/integration/extenders/UserTest.php index 28eb86fa33..a4108509a3 100644 --- a/tests/integration/extenders/UserTest.php +++ b/tests/integration/extenders/UserTest.php @@ -33,6 +33,9 @@ protected function prepDb() 'settings' => [ ['key' => 'display_name_driver', 'value' => 'custom'], ], + 'group_permission' => [ + ['permission' => 'viewUserList', 'group_id' => 3], + ] ]); }
a normal reply - too-obscure
private!