mirror of
https://github.com/flarum/core.git
synced 2025-01-17 22:29:15 +01:00
Model Visibility Scoping Extender and Tests (#2460)
This commit is contained in:
parent
e0437d237a
commit
8901073d12
@ -12,19 +12,49 @@ namespace Flarum\Database;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
61
src/Discussion/Access/ScopeDiscussionVisibility.php
Normal file
61
src/Discussion/Access/ScopeDiscussionVisibility.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Access;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ScopeDiscussionVisibility
|
||||
{
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function __invoke(User $actor, $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) {
|
||||
$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');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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 @@ class DiscussionPolicy extends AbstractPolicy
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
@ -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 @@ class DiscussionServiceProvider extends AbstractServiceProvider
|
||||
Renamed::class,
|
||||
DiscussionRenamedLogger::class
|
||||
);
|
||||
|
||||
Discussion::registerVisibilityScoper(new ScopeDiscussionVisibility(), 'view');
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
|
102
src/Extend/ModelVisibility.php
Normal file
102
src/Extend/ModelVisibility.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Exception;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
/**
|
||||
* Model visibility scoping allows us to scope queries based on the current user.
|
||||
* The main usage of this is only showing model instances that a user is allowed to see.
|
||||
*
|
||||
* This is done by running a query through a series of "scoper" callbacks, which apply
|
||||
* additional `where`s to the query based on the user.
|
||||
*
|
||||
* Scopers are classified under an ability. Calling `whereVisibleTo` on a query
|
||||
* will apply scopers under the `view` ability. Generally, the main `view` scopers
|
||||
* can request scoping with other abilities, which provides an entrypoint for extensions
|
||||
* to modify some restriction to a query.
|
||||
*
|
||||
* Scopers registered via `scopeAll` will apply to all queries under a model, regardless
|
||||
* of the ability, and will accept the ability name as an additional argument.
|
||||
*/
|
||||
class ModelVisibility implements ExtenderInterface
|
||||
{
|
||||
private $modelClass;
|
||||
private $scopers = [];
|
||||
private $allScopers = [];
|
||||
|
||||
/**
|
||||
* @param string $modelClass The ::class attribute of the model you are applying scopers to.
|
||||
* This model must extend from \Flarum\Database\AbstractModel,
|
||||
* and use \Flarum\Database\ScopeVisibilityTrait.
|
||||
*/
|
||||
public function __construct(string $modelClass)
|
||||
{
|
||||
$this->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));
|
||||
}
|
||||
}
|
||||
}
|
27
src/Group/Access/ScopeGroupVisibility.php
Normal file
27
src/Group/Access/ScopeGroupVisibility.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Group\Access;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ScopeGroupVisibility
|
||||
{
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function __invoke(User $actor, $query)
|
||||
{
|
||||
if ($actor->cannot('viewHiddenGroups')) {
|
||||
$query->where('is_hidden', false);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ namespace Flarum\Group;
|
||||
|
||||
use Flarum\User\AbstractPolicy;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class GroupPolicy extends AbstractPolicy
|
||||
{
|
||||
@ -31,15 +30,4 @@ class GroupPolicy extends AbstractPolicy
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function find(User $actor, Builder $query)
|
||||
{
|
||||
if ($actor->cannot('viewHiddenGroups')) {
|
||||
$query->where('is_hidden', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
namespace Flarum\Group;
|
||||
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Group\Access\ScopeGroupVisibility;
|
||||
|
||||
class GroupServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
@ -20,5 +21,7 @@ class GroupServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
$events = $this->app->make('events');
|
||||
$events->subscribe(GroupPolicy::class);
|
||||
|
||||
Group::registerVisibilityScoper(new ScopeGroupVisibility(), 'view');
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ namespace Flarum\Notification;
|
||||
|
||||
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 @@ class Notification extends AbstractModel
|
||||
->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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
62
src/Post/Access/ScopePostVisibility.php
Normal file
62
src/Post/Access/ScopePostVisibility.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Post\Access;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ScopePostVisibility
|
||||
{
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function __invoke(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');
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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 @@ class PostPolicy extends AbstractPolicy
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
@ -12,6 +12,7 @@ namespace Flarum\Post;
|
||||
use DateTime;
|
||||
use Flarum\Event\ConfigurePostTypes;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Post\Access\ScopePostVisibility;
|
||||
|
||||
class PostServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
@ -52,6 +53,8 @@ class PostServiceProvider extends AbstractServiceProvider
|
||||
|
||||
$events = $this->app->make('events');
|
||||
$events->subscribe(PostPolicy::class);
|
||||
|
||||
Post::registerVisibilityScoper(new ScopePostVisibility(), 'view');
|
||||
}
|
||||
|
||||
protected function setPostTypes()
|
||||
|
@ -54,6 +54,7 @@ abstract class AbstractPolicy
|
||||
|
||||
/**
|
||||
* @param ScopeModelVisibility $event
|
||||
* @deprecated beta 15, remove beta 16
|
||||
*/
|
||||
public function scopeModelVisibility(ScopeModelVisibility $event)
|
||||
{
|
||||
|
31
src/User/Access/ScopeUserVisibility.php
Normal file
31
src/User/Access/ScopeUserVisibility.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Access;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ScopeUserVisibility
|
||||
{
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function __invoke(User $actor, $query)
|
||||
{
|
||||
if ($actor->cannot('viewDiscussions')) {
|
||||
if ($actor->isGuest()) {
|
||||
$query->whereRaw('FALSE');
|
||||
} else {
|
||||
$query->where('id', $actor->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,8 +9,6 @@
|
||||
|
||||
namespace Flarum\User;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class UserPolicy extends AbstractPolicy
|
||||
{
|
||||
/**
|
||||
@ -29,19 +27,4 @@ class UserPolicy extends AbstractPolicy
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ namespace Flarum\User;
|
||||
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 @@ class UserServiceProvider extends AbstractServiceProvider
|
||||
User::registerPreference('discloseOnline', 'boolval', true);
|
||||
User::registerPreference('indexProfile', 'boolval', true);
|
||||
User::registerPreference('locale');
|
||||
|
||||
User::registerVisibilityScoper(new ScopeUserVisibility(), 'view');
|
||||
}
|
||||
}
|
||||
|
189
tests/integration/extenders/ModelVisibilityTest.php
Normal file
189
tests/integration/extenders/ModelVisibilityTest.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ModelVisibilityTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
protected function prepDb()
|
||||
{
|
||||
$this->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' => '<t><p>a normal reply - too-obscure</p></t>'],
|
||||
['id' => 2, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>private!</p></t>'],
|
||||
],
|
||||
'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);
|
||||
}
|
||||
}
|
@ -33,6 +33,9 @@ class UserTest extends TestCase
|
||||
'settings' => [
|
||||
['key' => 'display_name_driver', 'value' => 'custom'],
|
||||
],
|
||||
'group_permission' => [
|
||||
['permission' => 'viewUserList', 'group_id' => 3],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user