Skip to content

Commit

Permalink
- Move EntryRepository from Contracts to Stache directory
Browse files Browse the repository at this point in the history
- Remove custom permissions
- Introduce customizable restriction check
- Adjust EntryQueryBuilder and EntryPolicy to new restriction logic
  • Loading branch information
doefom committed May 26, 2024
1 parent e91de4b commit 8948cfd
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 73 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ _Restrict_ works by overriding and rebinding some classes in the addon's Service
```php
Statamic::repository(
\Statamic\Contracts\Entries\EntryRepository::class,
\Doefom\Restrict\Contracts\Entries\EntryRepository::class
\Doefom\Restrict\Stache\Repositories\EntryRepository::class
);

$this->app->bind(
Expand Down
18 changes: 18 additions & 0 deletions src/Facades/Restrict.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Doefom\Restrict\Facades;

use Illuminate\Support\Facades\Facade;

/**
* @mixin \Doefom\Restrict\Restrict
*/
class Restrict extends Facade
{

protected static function getFacadeAccessor(): string
{
return 'restrict';
}

}
7 changes: 4 additions & 3 deletions src/Policies/EntryPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Doefom\Restrict\Policies;

use Doefom\Restrict\Facades\Restrict;
use Statamic\Facades\User;
use Statamic\Policies\EntryPolicy as StatamicEntryPolicy;

Expand All @@ -13,10 +14,10 @@ public function view($user, $entry)

$default = parent::view($user, $entry);

if ($this->hasAnotherAuthor($user, $entry)) {
return $default && $user->hasPermission("view other authors' {$entry->collectionHandle()} entries");
if (! Restrict::isRestricted($user)) {
return $default;
}

return $default;
return $default && Restrict::isAuthorized($user, $entry);
}
}
66 changes: 66 additions & 0 deletions src/Restrict.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Doefom\Restrict;

use Illuminate\Support\Facades\Route;
use Statamic\Contracts\Auth\User;
use Statamic\Contracts\Entries\Entry;

class Restrict
{
protected $callback;

public function __construct()
{
// Default callback returns true which will ultimately not apply any
// restrictions and therefore preserve the default behavior.
$this->callback = fn (User $user, Entry $entry) => true;
}

/**
* Set the restriction callback used to determine if the current user is authorized to view the entry.
* The callback expects a User and Entry instance and should return a boolean.
*
* @param callable $callback
* @return void
*/
public function setRestriction(callable $callback): void
{
$this->callback = $callback;
}

/**
* Check if the current user is authorized to view the entry.
*
* @param User $user
* @param Entry $entry
* @return bool
*/
public function isAuthorized(User $user, Entry $entry): bool
{
return call_user_func($this->callback, $user, $entry);
}

/**
* Determine if the current user and route require restriction checks. Restriction
* checks are only required for authenticated CP routes and non-super users.
*
* @param User|null $user
* @return bool
*/
public function isRestricted(?User $user): bool
{
return $this->isAuthenticatedCpRoute() && $user && ! $user->isSuper();
}

/**
* Check if the current route is a CP route that requires authentication by checking
* if the 'statamic.cp.authenticated' middleware is applied to the route.
*
* @return bool
*/
protected function isAuthenticatedCpRoute(): bool
{
return in_array('statamic.cp.authenticated', Route::current()->gatherMiddleware());
}
}
15 changes: 6 additions & 9 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

namespace Doefom\Restrict;

use Statamic\Facades\Permission;
use Statamic\Providers\AddonServiceProvider;
use Statamic\Statamic;

class ServiceProvider extends AddonServiceProvider
{
public function register()
{
$this->app->singleton('restrict', fn() => new Restrict);
}

public function bootAddon(): void
{
Statamic::repository(
\Statamic\Contracts\Entries\EntryRepository::class,
\Doefom\Restrict\Contracts\Entries\EntryRepository::class
\Doefom\Restrict\Stache\Repositories\EntryRepository::class
);

// Note: This is just a precaution, as the EntryRepository would already make sure that unauthorized entries
Expand All @@ -21,12 +25,5 @@ public function bootAddon(): void
\Statamic\Policies\EntryPolicy::class,
\Doefom\Restrict\Policies\EntryPolicy::class
);

Permission::extend(function () {
Permission::get('view {collection} entries')->addChild(
Permission::make("view other authors' {collection} entries")
->label("View other authors' entries")
);
});
}
}
64 changes: 5 additions & 59 deletions src/Stache/Query/EntryQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,29 @@

namespace Doefom\Restrict\Stache\Query;

use Illuminate\Support\Facades\Route;
use Statamic\Contracts\Auth\User as UserContract;
use Statamic\Entries\Entry;
use Doefom\Restrict\Facades\Restrict;
use Statamic\Facades\User;
use Statamic\Stache\Query\EntryQueryBuilder as StatamicEntryQueryBuilder;
use Statamic\Support\Str;

class EntryQueryBuilder extends StatamicEntryQueryBuilder
{

protected function getFilteredKeys()
{
$resKeys = parent::getFilteredKeys();

$user = User::current();

if ($user === null) {
if (! Restrict::isRestricted($user)) {
return $resKeys;
}

// Only apply the restriction if:
// 1. The current route is a CP route that requires authentication
// 2. The current user is not a super user
if (! $this->isAuthenticatedCpRoute() || $user->isSuper()) {
return $resKeys;
}

// Note: At this point, we know there is an authenticated user.
// Note: At this point, we know there is an authenticated user that has restricted access.
return $resKeys->filter(function ($key) use ($user) {
$entry = $this->store->getItem($key);

return $this->isAuthorized($user, $entry);
return Restrict::isAuthorized($user, $entry);
});
}

/**
* Check if the current user is authorized to view the entry. This is done by checking if the user has permission to
* view other authors' entries in the entry's collection or if the user is the author of the entry.
*/
protected function isAuthorized(UserContract $user, Entry $entry): bool
{
if ($this->hasAnotherAuthor($user, $entry)) {
return $this->isInAuthorizedCollections($user, $entry);
}

return true;
}

protected function isInAuthorizedCollections(UserContract $user, Entry $entry): bool
{
// Get all collections where the user has permission to view other authors' entries
$authorizedCollections = $user->permissions()
->filter(fn ($permission) => Str::contains($permission, "view other authors'"))
->map(function ($permission) {
return Str::between($permission, "view other authors' ", ' entries');
})
->values()
->toArray();

// Check if the entry's collection is in the list of authorized collections
return in_array($entry->collectionHandle(), $authorizedCollections);
}

/**
* Check if the current route is a CP route that requires authentication by checking if the
* 'statamic.cp.authenticated' middleware is applied to the route.
*/
protected function isAuthenticatedCpRoute(): bool
{
return in_array('statamic.cp.authenticated', Route::current()->gatherMiddleware());
}

protected function hasAnotherAuthor(UserContract $user, $entry): bool
{
if ($entry->blueprint()->hasField('author') === false) {
return false;
}

return ! $entry->authors()->contains($user->id());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Doefom\Restrict\Contracts\Entries;
namespace Doefom\Restrict\Stache\Repositories;

use Doefom\Restrict\Stache\Query\EntryQueryBuilder;
use Statamic\Contracts\Entries\QueryBuilder;
Expand Down

0 comments on commit 8948cfd

Please sign in to comment.