Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7aa69fa
Only perform super user check in `Gate::before()` when ability is a S…
duncanmcclean Feb 28, 2025
39a696e
Ensure permissions are booted before authorization is handled
duncanmcclean Feb 28, 2025
f34eafa
Remove commented out `dd()` from middleware 😄
duncanmcclean Feb 28, 2025
639a059
Add super user check to the `before` method in authorization policies.
duncanmcclean Feb 28, 2025
8fa1c6b
Ensure permissions exist, otherwise the super user check will fail.
duncanmcclean Feb 28, 2025
1b91834
Move the middleware back to where they were originally.
duncanmcclean Feb 28, 2025
7770675
Boot permissions in `Gate::before()` method...
duncanmcclean Feb 28, 2025
1995374
Update an existing test
duncanmcclean Feb 28, 2025
b365909
Fix super authorization in AssetFolderPolicy
duncanmcclean Feb 28, 2025
f23a4c9
Merge remote-tracking branch 'origin/master' into super-user-authoriz…
duncanmcclean Mar 6, 2025
3ce7705
Return early when permissions have already been booted.
duncanmcclean Mar 6, 2025
88e702f
Move super user check to `Gate::after()`
duncanmcclean Mar 7, 2025
d9b9567
Refactor super user / permission check
duncanmcclean Mar 7, 2025
1327752
Re-work the logic a little to allow for wildcard permissions
duncanmcclean Mar 7, 2025
ee19523
Register permission in test
duncanmcclean Mar 7, 2025
7abb312
Merge branch 'master' into super-user-authorization
jasonvarga Mar 11, 2025
de15f90
Test for booting once
jasonvarga Mar 11, 2025
f59a7e4
nitpick
jasonvarga Mar 11, 2025
0803113
Add gate tests
jasonvarga Mar 12, 2025
99d8acf
Add a flattened method and use that to check if a given permission is…
jasonvarga Mar 12, 2025
ca39b67
fix test failures ...
jasonvarga Mar 12, 2025
34d421c
These tests aren't concerned with permissions so just remove the cans…
jasonvarga Mar 13, 2025
705cc02
Test gate should not explicitly deny
jasonvarga Mar 13, 2025
fd7b7cb
User clearer names
jasonvarga Mar 13, 2025
2de3e31
Avoid even checking if user has permission if that ability is not a r…
jasonvarga Mar 13, 2025
c04820e
Bring back existing can() usage, but with an actual gate policy, sinc…
jasonvarga Mar 13, 2025
3e87ca5
Don't just register the successful one, and add a note.
jasonvarga Mar 13, 2025
691a0a8
Make chainable
jasonvarga Mar 13, 2025
91162f8
Explain
jasonvarga Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/Auth/Permission.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,50 @@ public function permissions()
})->values();
}

public function flattened()
{
if (! $this->callback) {
return [
$this,
...$this->children()->map(function ($child) {
return (new self)
->value($child->value())
->label($child->label())
->placeholder($this->placeholder)
->placeholderLabel($this->placeholderLabel)
->placeholderValue($this->placeholderValue)
->children($child->children()->all())
->group($this->group());
})->flatMap->flattened()->all(),
];
}

$items = call_user_func($this->callback);

return collect($items)->flatMap(function ($replacement) {
$replaced = (new self)
->value($this->value)
->label($this->label)
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->group($this->group());

$children = $this->children()->map(function ($child) use ($replacement) {
return (new self)
->value($child->originalValue())
->label($child->originalLabel())
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->children($child->children()->all())
->group($this->group());
});

return [$replaced, ...$children->flatMap->flattened()->all()];
})->values();
}

public function children(?array $children = null)
{
return $this
Expand Down
13 changes: 13 additions & 0 deletions src/Auth/Permissions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,14 @@ class Permissions
protected $permissions = [];
protected $groups = [];
protected $pendingGroup = null;
protected $booted = false;

public function boot()
{
if ($this->booted) {
return $this;
}

$early = $this->permissions;
$this->permissions = [];

Expand All@@ -23,6 +28,9 @@ public function boot()
}

$this->permissions = array_merge($this->permissions, $early);
$this->booted = true;

return $this;
}

public function extend($callback)
Expand DownExpand Up@@ -125,4 +133,9 @@ public function group($name, $label, $permissions = null)

$this->pendingGroup = null;
}

public function flattened()
{
return collect($this->permissions)->flatMap->flattened();
}
}
1 change: 0 additions & 1 deletion src/Http/Middleware/CP/Authorize.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,6 @@ public function handle($request, Closure $next)
}

if ($user->cant('access cp')) {
// dd('theres a user but they are unauthorized', $user);
throw new AuthorizationException('Unauthorized.');
}

Expand Down
4 changes: 3 additions & 1 deletion src/Policies/AssetContainerPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ class AssetContainerPolicy
{
public function before($user, $ability)
{
if (User::fromUser($user)->hasPermission('configure asset containers')) {
$user = User::fromUser($user);

if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/AssetFolderPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@

class AssetFolderPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function create($user, $assetContainer)
{
$user = User::fromUser($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/AssetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure asset containers')) {
if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/CollectionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/EntryPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FieldsetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability, $fieldset)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure fields')) {
if ($user->isSuper() || $user->hasPermission('configure fields')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormSubmissionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/GlobalSetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure globals')) {
if ($user->isSuper() || $user->hasPermission('configure globals')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/NavPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure navs')) {
if ($user->isSuper() || $user->hasPermission('configure navs')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/NavTreePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@ class NavTreePolicy extends NavPolicy
{
use Concerns\HasMultisitePolicy;

public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $nav)
{
$user = User::fromUser($user);
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/SitePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@

class SitePolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $site)
{
if (! Site::multiEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TaxonomyPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TermPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,13 @@

class UserPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function index($authed)
{
$authed = User::fromUser($authed);
Expand Down
20 changes: 15 additions & 5 deletions src/Providers/AuthServiceProvider.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@
use Statamic\Contracts\Auth\RoleRepository;
use Statamic\Contracts\Auth\UserGroupRepository;
use Statamic\Contracts\Auth\UserRepository;
use Statamic\Facades\Permission;
use Statamic\Facades\User;
use Statamic\Policies;

Expand DownExpand Up@@ -83,12 +84,21 @@ public function boot()
return new UserProvider;
});

Gate::before(function ($user, $ability) {
return optional(User::fromUser($user))->isSuper() ? true : null;
});

Gate::after(function ($user, $ability) {
return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null;
// If the ability isn't a Statamic permission, we don't want to get involved. 🙈
if (! Permission::boot()->flattened()->map->value()->contains($ability)) {
return null;
}

$user = User::fromUser($user);

if ($user->isSuper()) {
return true;
}

if ($user->hasPermission($ability)) {
return true;
}
});

foreach ($this->policies as $key => $policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/Sites/Site.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public function handle()

public function name()
{
return $this->config['name'];
return $this->config['name'] ?? $this->handle();
}

public function locale()
Expand Down
30 changes: 15 additions & 15 deletions tests/CP/Navigation/ActiveNavItemTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,8 +222,8 @@ public function it_can_check_if_parent_extension_with_array_based_children_item_
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -247,8 +247,8 @@ public function it_can_check_when_parent_and_array_based_child_extension_items_a
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -272,8 +272,8 @@ public function it_can_check_when_parent_and_array_based_descendant_of_child_ext
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -298,9 +298,9 @@ public function it_builds_extension_children_closure_when_not_active()
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/')->can('view seo reports'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults')->can('edit seo site defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -324,8 +324,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -351,8 +351,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -378,8 +378,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7aa69fa
Only perform super user check in `Gate::before()` when ability is a S…
duncanmcclean Feb 28, 2025
39a696e
Ensure permissions are booted before authorization is handled
duncanmcclean Feb 28, 2025
f34eafa
Remove commented out `dd()` from middleware 😄
duncanmcclean Feb 28, 2025
639a059
Add super user check to the `before` method in authorization policies.
duncanmcclean Feb 28, 2025
8fa1c6b
Ensure permissions exist, otherwise the super user check will fail.
duncanmcclean Feb 28, 2025
1b91834
Move the middleware back to where they were originally.
duncanmcclean Feb 28, 2025
7770675
Boot permissions in `Gate::before()` method...
duncanmcclean Feb 28, 2025
1995374
Update an existing test
duncanmcclean Feb 28, 2025
b365909
Fix super authorization in AssetFolderPolicy
duncanmcclean Feb 28, 2025
f23a4c9
Merge remote-tracking branch 'origin/master' into super-user-authoriz…
duncanmcclean Mar 6, 2025
3ce7705
Return early when permissions have already been booted.
duncanmcclean Mar 6, 2025
88e702f
Move super user check to `Gate::after()`
duncanmcclean Mar 7, 2025
d9b9567
Refactor super user / permission check
duncanmcclean Mar 7, 2025
1327752
Re-work the logic a little to allow for wildcard permissions
duncanmcclean Mar 7, 2025
ee19523
Register permission in test
duncanmcclean Mar 7, 2025
7abb312
Merge branch 'master' into super-user-authorization
jasonvarga Mar 11, 2025
de15f90
Test for booting once
jasonvarga Mar 11, 2025
f59a7e4
nitpick
jasonvarga Mar 11, 2025
0803113
Add gate tests
jasonvarga Mar 12, 2025
99d8acf
Add a flattened method and use that to check if a given permission is…
jasonvarga Mar 12, 2025
ca39b67
fix test failures ...
jasonvarga Mar 12, 2025
34d421c
These tests aren't concerned with permissions so just remove the cans…
jasonvarga Mar 13, 2025
705cc02
Test gate should not explicitly deny
jasonvarga Mar 13, 2025
fd7b7cb
User clearer names
jasonvarga Mar 13, 2025
2de3e31
Avoid even checking if user has permission if that ability is not a r…
jasonvarga Mar 13, 2025
c04820e
Bring back existing can() usage, but with an actual gate policy, sinc…
jasonvarga Mar 13, 2025
3e87ca5
Don't just register the successful one, and add a note.
jasonvarga Mar 13, 2025
691a0a8
Make chainable
jasonvarga Mar 13, 2025
91162f8
Explain
jasonvarga Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/Auth/Permission.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,50 @@ public function permissions()
})->values();
}

public function flattened()
{
if (! $this->callback) {
return [
$this,
...$this->children()->map(function ($child) {
return (new self)
->value($child->value())
->label($child->label())
->placeholder($this->placeholder)
->placeholderLabel($this->placeholderLabel)
->placeholderValue($this->placeholderValue)
->children($child->children()->all())
->group($this->group());
})->flatMap->flattened()->all(),
];
}

$items = call_user_func($this->callback);

return collect($items)->flatMap(function ($replacement) {
$replaced = (new self)
->value($this->value)
->label($this->label)
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->group($this->group());

$children = $this->children()->map(function ($child) use ($replacement) {
return (new self)
->value($child->originalValue())
->label($child->originalLabel())
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->children($child->children()->all())
->group($this->group());
});

return [$replaced, ...$children->flatMap->flattened()->all()];
})->values();
}

public function children(?array $children = null)
{
return $this
Expand Down
13 changes: 13 additions & 0 deletions src/Auth/Permissions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,14 @@ class Permissions
protected $permissions = [];
protected $groups = [];
protected $pendingGroup = null;
protected $booted = false;

public function boot()
{
if ($this->booted) {
return $this;
}

$early = $this->permissions;
$this->permissions = [];

Expand All@@ -23,6 +28,9 @@ public function boot()
}

$this->permissions = array_merge($this->permissions, $early);
$this->booted = true;

return $this;
}

public function extend($callback)
Expand DownExpand Up@@ -125,4 +133,9 @@ public function group($name, $label, $permissions = null)

$this->pendingGroup = null;
}

public function flattened()
{
return collect($this->permissions)->flatMap->flattened();
}
}
1 change: 0 additions & 1 deletion src/Http/Middleware/CP/Authorize.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,6 @@ public function handle($request, Closure $next)
}

if ($user->cant('access cp')) {
// dd('theres a user but they are unauthorized', $user);
throw new AuthorizationException('Unauthorized.');
}

Expand Down
4 changes: 3 additions & 1 deletion src/Policies/AssetContainerPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ class AssetContainerPolicy
{
public function before($user, $ability)
{
if (User::fromUser($user)->hasPermission('configure asset containers')) {
$user = User::fromUser($user);

if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/AssetFolderPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@

class AssetFolderPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function create($user, $assetContainer)
{
$user = User::fromUser($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/AssetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure asset containers')) {
if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/CollectionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/EntryPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FieldsetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability, $fieldset)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure fields')) {
if ($user->isSuper() || $user->hasPermission('configure fields')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormSubmissionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/GlobalSetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure globals')) {
if ($user->isSuper() || $user->hasPermission('configure globals')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/NavPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure navs')) {
if ($user->isSuper() || $user->hasPermission('configure navs')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/NavTreePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@ class NavTreePolicy extends NavPolicy
{
use Concerns\HasMultisitePolicy;

public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $nav)
{
$user = User::fromUser($user);
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/SitePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@

class SitePolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $site)
{
if (! Site::multiEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TaxonomyPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TermPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,13 @@

class UserPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function index($authed)
{
$authed = User::fromUser($authed);
Expand Down
20 changes: 15 additions & 5 deletions src/Providers/AuthServiceProvider.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@
use Statamic\Contracts\Auth\RoleRepository;
use Statamic\Contracts\Auth\UserGroupRepository;
use Statamic\Contracts\Auth\UserRepository;
use Statamic\Facades\Permission;
use Statamic\Facades\User;
use Statamic\Policies;

Expand DownExpand Up@@ -83,12 +84,21 @@ public function boot()
return new UserProvider;
});

Gate::before(function ($user, $ability) {
return optional(User::fromUser($user))->isSuper() ? true : null;
});

Gate::after(function ($user, $ability) {
return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null;
// If the ability isn't a Statamic permission, we don't want to get involved. 🙈
if (! Permission::boot()->flattened()->map->value()->contains($ability)) {
return null;
}

$user = User::fromUser($user);

if ($user->isSuper()) {
return true;
}

if ($user->hasPermission($ability)) {
return true;
}
});

foreach ($this->policies as $key => $policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/Sites/Site.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public function handle()

public function name()
{
return $this->config['name'];
return $this->config['name'] ?? $this->handle();
}

public function locale()
Expand Down
30 changes: 15 additions & 15 deletions tests/CP/Navigation/ActiveNavItemTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,8 +222,8 @@ public function it_can_check_if_parent_extension_with_array_based_children_item_
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -247,8 +247,8 @@ public function it_can_check_when_parent_and_array_based_child_extension_items_a
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -272,8 +272,8 @@ public function it_can_check_when_parent_and_array_based_descendant_of_child_ext
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -298,9 +298,9 @@ public function it_builds_extension_children_closure_when_not_active()
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/')->can('view seo reports'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults')->can('edit seo site defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -324,8 +324,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -351,8 +351,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -378,8 +378,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7aa69fa
Only perform super user check in `Gate::before()` when ability is a S…
duncanmcclean Feb 28, 2025
39a696e
Ensure permissions are booted before authorization is handled
duncanmcclean Feb 28, 2025
f34eafa
Remove commented out `dd()` from middleware 😄
duncanmcclean Feb 28, 2025
639a059
Add super user check to the `before` method in authorization policies.
duncanmcclean Feb 28, 2025
8fa1c6b
Ensure permissions exist, otherwise the super user check will fail.
duncanmcclean Feb 28, 2025
1b91834
Move the middleware back to where they were originally.
duncanmcclean Feb 28, 2025
7770675
Boot permissions in `Gate::before()` method...
duncanmcclean Feb 28, 2025
1995374
Update an existing test
duncanmcclean Feb 28, 2025
b365909
Fix super authorization in AssetFolderPolicy
duncanmcclean Feb 28, 2025
f23a4c9
Merge remote-tracking branch 'origin/master' into super-user-authoriz…
duncanmcclean Mar 6, 2025
3ce7705
Return early when permissions have already been booted.
duncanmcclean Mar 6, 2025
88e702f
Move super user check to `Gate::after()`
duncanmcclean Mar 7, 2025
d9b9567
Refactor super user / permission check
duncanmcclean Mar 7, 2025
1327752
Re-work the logic a little to allow for wildcard permissions
duncanmcclean Mar 7, 2025
ee19523
Register permission in test
duncanmcclean Mar 7, 2025
7abb312
Merge branch 'master' into super-user-authorization
jasonvarga Mar 11, 2025
de15f90
Test for booting once
jasonvarga Mar 11, 2025
f59a7e4
nitpick
jasonvarga Mar 11, 2025
0803113
Add gate tests
jasonvarga Mar 12, 2025
99d8acf
Add a flattened method and use that to check if a given permission is…
jasonvarga Mar 12, 2025
ca39b67
fix test failures ...
jasonvarga Mar 12, 2025
34d421c
These tests aren't concerned with permissions so just remove the cans…
jasonvarga Mar 13, 2025
705cc02
Test gate should not explicitly deny
jasonvarga Mar 13, 2025
fd7b7cb
User clearer names
jasonvarga Mar 13, 2025
2de3e31
Avoid even checking if user has permission if that ability is not a r…
jasonvarga Mar 13, 2025
c04820e
Bring back existing can() usage, but with an actual gate policy, sinc…
jasonvarga Mar 13, 2025
3e87ca5
Don't just register the successful one, and add a note.
jasonvarga Mar 13, 2025
691a0a8
Make chainable
jasonvarga Mar 13, 2025
91162f8
Explain
jasonvarga Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/Auth/Permission.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,50 @@ public function permissions()
})->values();
}

public function flattened()
{
if (! $this->callback) {
return [
$this,
...$this->children()->map(function ($child) {
return (new self)
->value($child->value())
->label($child->label())
->placeholder($this->placeholder)
->placeholderLabel($this->placeholderLabel)
->placeholderValue($this->placeholderValue)
->children($child->children()->all())
->group($this->group());
})->flatMap->flattened()->all(),
];
}

$items = call_user_func($this->callback);

return collect($items)->flatMap(function ($replacement) {
$replaced = (new self)
->value($this->value)
->label($this->label)
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->group($this->group());

$children = $this->children()->map(function ($child) use ($replacement) {
return (new self)
->value($child->originalValue())
->label($child->originalLabel())
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->children($child->children()->all())
->group($this->group());
});

return [$replaced, ...$children->flatMap->flattened()->all()];
})->values();
}

public function children(?array $children = null)
{
return $this
Expand Down
13 changes: 13 additions & 0 deletions src/Auth/Permissions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,14 @@ class Permissions
protected $permissions = [];
protected $groups = [];
protected $pendingGroup = null;
protected $booted = false;

public function boot()
{
if ($this->booted) {
return $this;
}

$early = $this->permissions;
$this->permissions = [];

Expand All@@ -23,6 +28,9 @@ public function boot()
}

$this->permissions = array_merge($this->permissions, $early);
$this->booted = true;

return $this;
}

public function extend($callback)
Expand DownExpand Up@@ -125,4 +133,9 @@ public function group($name, $label, $permissions = null)

$this->pendingGroup = null;
}

public function flattened()
{
return collect($this->permissions)->flatMap->flattened();
}
}
1 change: 0 additions & 1 deletion src/Http/Middleware/CP/Authorize.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,6 @@ public function handle($request, Closure $next)
}

if ($user->cant('access cp')) {
// dd('theres a user but they are unauthorized', $user);
throw new AuthorizationException('Unauthorized.');
}

Expand Down
4 changes: 3 additions & 1 deletion src/Policies/AssetContainerPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ class AssetContainerPolicy
{
public function before($user, $ability)
{
if (User::fromUser($user)->hasPermission('configure asset containers')) {
$user = User::fromUser($user);

if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/AssetFolderPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@

class AssetFolderPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function create($user, $assetContainer)
{
$user = User::fromUser($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/AssetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure asset containers')) {
if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/CollectionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/EntryPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FieldsetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability, $fieldset)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure fields')) {
if ($user->isSuper() || $user->hasPermission('configure fields')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormSubmissionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/GlobalSetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure globals')) {
if ($user->isSuper() || $user->hasPermission('configure globals')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/NavPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure navs')) {
if ($user->isSuper() || $user->hasPermission('configure navs')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/NavTreePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@ class NavTreePolicy extends NavPolicy
{
use Concerns\HasMultisitePolicy;

public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $nav)
{
$user = User::fromUser($user);
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/SitePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@

class SitePolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $site)
{
if (! Site::multiEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TaxonomyPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TermPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,13 @@

class UserPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function index($authed)
{
$authed = User::fromUser($authed);
Expand Down
20 changes: 15 additions & 5 deletions src/Providers/AuthServiceProvider.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@
use Statamic\Contracts\Auth\RoleRepository;
use Statamic\Contracts\Auth\UserGroupRepository;
use Statamic\Contracts\Auth\UserRepository;
use Statamic\Facades\Permission;
use Statamic\Facades\User;
use Statamic\Policies;

Expand DownExpand Up@@ -83,12 +84,21 @@ public function boot()
return new UserProvider;
});

Gate::before(function ($user, $ability) {
return optional(User::fromUser($user))->isSuper() ? true : null;
});

Gate::after(function ($user, $ability) {
return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null;
// If the ability isn't a Statamic permission, we don't want to get involved. 🙈
if (! Permission::boot()->flattened()->map->value()->contains($ability)) {
return null;
}

$user = User::fromUser($user);

if ($user->isSuper()) {
return true;
}

if ($user->hasPermission($ability)) {
return true;
}
});

foreach ($this->policies as $key => $policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/Sites/Site.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public function handle()

public function name()
{
return $this->config['name'];
return $this->config['name'] ?? $this->handle();
}

public function locale()
Expand Down
30 changes: 15 additions & 15 deletions tests/CP/Navigation/ActiveNavItemTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,8 +222,8 @@ public function it_can_check_if_parent_extension_with_array_based_children_item_
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -247,8 +247,8 @@ public function it_can_check_when_parent_and_array_based_child_extension_items_a
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -272,8 +272,8 @@ public function it_can_check_when_parent_and_array_based_descendant_of_child_ext
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -298,9 +298,9 @@ public function it_builds_extension_children_closure_when_not_active()
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/')->can('view seo reports'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults')->can('edit seo site defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -324,8 +324,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -351,8 +351,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -378,8 +378,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7aa69fa
Only perform super user check in `Gate::before()` when ability is a S…
duncanmcclean Feb 28, 2025
39a696e
Ensure permissions are booted before authorization is handled
duncanmcclean Feb 28, 2025
f34eafa
Remove commented out `dd()` from middleware 😄
duncanmcclean Feb 28, 2025
639a059
Add super user check to the `before` method in authorization policies.
duncanmcclean Feb 28, 2025
8fa1c6b
Ensure permissions exist, otherwise the super user check will fail.
duncanmcclean Feb 28, 2025
1b91834
Move the middleware back to where they were originally.
duncanmcclean Feb 28, 2025
7770675
Boot permissions in `Gate::before()` method...
duncanmcclean Feb 28, 2025
1995374
Update an existing test
duncanmcclean Feb 28, 2025
b365909
Fix super authorization in AssetFolderPolicy
duncanmcclean Feb 28, 2025
f23a4c9
Merge remote-tracking branch 'origin/master' into super-user-authoriz…
duncanmcclean Mar 6, 2025
3ce7705
Return early when permissions have already been booted.
duncanmcclean Mar 6, 2025
88e702f
Move super user check to `Gate::after()`
duncanmcclean Mar 7, 2025
d9b9567
Refactor super user / permission check
duncanmcclean Mar 7, 2025
1327752
Re-work the logic a little to allow for wildcard permissions
duncanmcclean Mar 7, 2025
ee19523
Register permission in test
duncanmcclean Mar 7, 2025
7abb312
Merge branch 'master' into super-user-authorization
jasonvarga Mar 11, 2025
de15f90
Test for booting once
jasonvarga Mar 11, 2025
f59a7e4
nitpick
jasonvarga Mar 11, 2025
0803113
Add gate tests
jasonvarga Mar 12, 2025
99d8acf
Add a flattened method and use that to check if a given permission is…
jasonvarga Mar 12, 2025
ca39b67
fix test failures ...
jasonvarga Mar 12, 2025
34d421c
These tests aren't concerned with permissions so just remove the cans…
jasonvarga Mar 13, 2025
705cc02
Test gate should not explicitly deny
jasonvarga Mar 13, 2025
fd7b7cb
User clearer names
jasonvarga Mar 13, 2025
2de3e31
Avoid even checking if user has permission if that ability is not a r…
jasonvarga Mar 13, 2025
c04820e
Bring back existing can() usage, but with an actual gate policy, sinc…
jasonvarga Mar 13, 2025
3e87ca5
Don't just register the successful one, and add a note.
jasonvarga Mar 13, 2025
691a0a8
Make chainable
jasonvarga Mar 13, 2025
91162f8
Explain
jasonvarga Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/Auth/Permission.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,50 @@ public function permissions()
})->values();
}

public function flattened()
{
if (! $this->callback) {
return [
$this,
...$this->children()->map(function ($child) {
return (new self)
->value($child->value())
->label($child->label())
->placeholder($this->placeholder)
->placeholderLabel($this->placeholderLabel)
->placeholderValue($this->placeholderValue)
->children($child->children()->all())
->group($this->group());
})->flatMap->flattened()->all(),
];
}

$items = call_user_func($this->callback);

return collect($items)->flatMap(function ($replacement) {
$replaced = (new self)
->value($this->value)
->label($this->label)
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->group($this->group());

$children = $this->children()->map(function ($child) use ($replacement) {
return (new self)
->value($child->originalValue())
->label($child->originalLabel())
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->children($child->children()->all())
->group($this->group());
});

return [$replaced, ...$children->flatMap->flattened()->all()];
})->values();
}

public function children(?array $children = null)
{
return $this
Expand Down
13 changes: 13 additions & 0 deletions src/Auth/Permissions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,14 @@ class Permissions
protected $permissions = [];
protected $groups = [];
protected $pendingGroup = null;
protected $booted = false;

public function boot()
{
if ($this->booted) {
return $this;
}

$early = $this->permissions;
$this->permissions = [];

Expand All@@ -23,6 +28,9 @@ public function boot()
}

$this->permissions = array_merge($this->permissions, $early);
$this->booted = true;

return $this;
}

public function extend($callback)
Expand DownExpand Up@@ -125,4 +133,9 @@ public function group($name, $label, $permissions = null)

$this->pendingGroup = null;
}

public function flattened()
{
return collect($this->permissions)->flatMap->flattened();
}
}
1 change: 0 additions & 1 deletion src/Http/Middleware/CP/Authorize.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,6 @@ public function handle($request, Closure $next)
}

if ($user->cant('access cp')) {
// dd('theres a user but they are unauthorized', $user);
throw new AuthorizationException('Unauthorized.');
}

Expand Down
4 changes: 3 additions & 1 deletion src/Policies/AssetContainerPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ class AssetContainerPolicy
{
public function before($user, $ability)
{
if (User::fromUser($user)->hasPermission('configure asset containers')) {
$user = User::fromUser($user);

if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/AssetFolderPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@

class AssetFolderPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function create($user, $assetContainer)
{
$user = User::fromUser($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/AssetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure asset containers')) {
if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/CollectionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/EntryPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FieldsetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability, $fieldset)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure fields')) {
if ($user->isSuper() || $user->hasPermission('configure fields')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormSubmissionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/GlobalSetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure globals')) {
if ($user->isSuper() || $user->hasPermission('configure globals')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/NavPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure navs')) {
if ($user->isSuper() || $user->hasPermission('configure navs')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/NavTreePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@ class NavTreePolicy extends NavPolicy
{
use Concerns\HasMultisitePolicy;

public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $nav)
{
$user = User::fromUser($user);
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/SitePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@

class SitePolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $site)
{
if (! Site::multiEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TaxonomyPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TermPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,13 @@

class UserPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function index($authed)
{
$authed = User::fromUser($authed);
Expand Down
20 changes: 15 additions & 5 deletions src/Providers/AuthServiceProvider.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@
use Statamic\Contracts\Auth\RoleRepository;
use Statamic\Contracts\Auth\UserGroupRepository;
use Statamic\Contracts\Auth\UserRepository;
use Statamic\Facades\Permission;
use Statamic\Facades\User;
use Statamic\Policies;

Expand DownExpand Up@@ -83,12 +84,21 @@ public function boot()
return new UserProvider;
});

Gate::before(function ($user, $ability) {
return optional(User::fromUser($user))->isSuper() ? true : null;
});

Gate::after(function ($user, $ability) {
return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null;
// If the ability isn't a Statamic permission, we don't want to get involved. 🙈
if (! Permission::boot()->flattened()->map->value()->contains($ability)) {
return null;
}

$user = User::fromUser($user);

if ($user->isSuper()) {
return true;
}

if ($user->hasPermission($ability)) {
return true;
}
});

foreach ($this->policies as $key => $policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/Sites/Site.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public function handle()

public function name()
{
return $this->config['name'];
return $this->config['name'] ?? $this->handle();
}

public function locale()
Expand Down
30 changes: 15 additions & 15 deletions tests/CP/Navigation/ActiveNavItemTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,8 +222,8 @@ public function it_can_check_if_parent_extension_with_array_based_children_item_
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -247,8 +247,8 @@ public function it_can_check_when_parent_and_array_based_child_extension_items_a
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -272,8 +272,8 @@ public function it_can_check_when_parent_and_array_based_descendant_of_child_ext
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -298,9 +298,9 @@ public function it_builds_extension_children_closure_when_not_active()
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/')->can('view seo reports'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults')->can('edit seo site defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -324,8 +324,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -351,8 +351,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -378,8 +378,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7aa69fa
Only perform super user check in `Gate::before()` when ability is a S…
duncanmcclean Feb 28, 2025
39a696e
Ensure permissions are booted before authorization is handled
duncanmcclean Feb 28, 2025
f34eafa
Remove commented out `dd()` from middleware 😄
duncanmcclean Feb 28, 2025
639a059
Add super user check to the `before` method in authorization policies.
duncanmcclean Feb 28, 2025
8fa1c6b
Ensure permissions exist, otherwise the super user check will fail.
duncanmcclean Feb 28, 2025
1b91834
Move the middleware back to where they were originally.
duncanmcclean Feb 28, 2025
7770675
Boot permissions in `Gate::before()` method...
duncanmcclean Feb 28, 2025
1995374
Update an existing test
duncanmcclean Feb 28, 2025
b365909
Fix super authorization in AssetFolderPolicy
duncanmcclean Feb 28, 2025
f23a4c9
Merge remote-tracking branch 'origin/master' into super-user-authoriz…
duncanmcclean Mar 6, 2025
3ce7705
Return early when permissions have already been booted.
duncanmcclean Mar 6, 2025
88e702f
Move super user check to `Gate::after()`
duncanmcclean Mar 7, 2025
d9b9567
Refactor super user / permission check
duncanmcclean Mar 7, 2025
1327752
Re-work the logic a little to allow for wildcard permissions
duncanmcclean Mar 7, 2025
ee19523
Register permission in test
duncanmcclean Mar 7, 2025
7abb312
Merge branch 'master' into super-user-authorization
jasonvarga Mar 11, 2025
de15f90
Test for booting once
jasonvarga Mar 11, 2025
f59a7e4
nitpick
jasonvarga Mar 11, 2025
0803113
Add gate tests
jasonvarga Mar 12, 2025
99d8acf
Add a flattened method and use that to check if a given permission is…
jasonvarga Mar 12, 2025
ca39b67
fix test failures ...
jasonvarga Mar 12, 2025
34d421c
These tests aren't concerned with permissions so just remove the cans…
jasonvarga Mar 13, 2025
705cc02
Test gate should not explicitly deny
jasonvarga Mar 13, 2025
fd7b7cb
User clearer names
jasonvarga Mar 13, 2025
2de3e31
Avoid even checking if user has permission if that ability is not a r…
jasonvarga Mar 13, 2025
c04820e
Bring back existing can() usage, but with an actual gate policy, sinc…
jasonvarga Mar 13, 2025
3e87ca5
Don't just register the successful one, and add a note.
jasonvarga Mar 13, 2025
691a0a8
Make chainable
jasonvarga Mar 13, 2025
91162f8
Explain
jasonvarga Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/Auth/Permission.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,50 @@ public function permissions()
})->values();
}

public function flattened()
{
if (! $this->callback) {
return [
$this,
...$this->children()->map(function ($child) {
return (new self)
->value($child->value())
->label($child->label())
->placeholder($this->placeholder)
->placeholderLabel($this->placeholderLabel)
->placeholderValue($this->placeholderValue)
->children($child->children()->all())
->group($this->group());
})->flatMap->flattened()->all(),
];
}

$items = call_user_func($this->callback);

return collect($items)->flatMap(function ($replacement) {
$replaced = (new self)
->value($this->value)
->label($this->label)
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->group($this->group());

$children = $this->children()->map(function ($child) use ($replacement) {
return (new self)
->value($child->originalValue())
->label($child->originalLabel())
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->children($child->children()->all())
->group($this->group());
});

return [$replaced, ...$children->flatMap->flattened()->all()];
})->values();
}

public function children(?array $children = null)
{
return $this
Expand Down
13 changes: 13 additions & 0 deletions src/Auth/Permissions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,14 @@ class Permissions
protected $permissions = [];
protected $groups = [];
protected $pendingGroup = null;
protected $booted = false;

public function boot()
{
if ($this->booted) {
return $this;
}

$early = $this->permissions;
$this->permissions = [];

Expand All@@ -23,6 +28,9 @@ public function boot()
}

$this->permissions = array_merge($this->permissions, $early);
$this->booted = true;

return $this;
}

public function extend($callback)
Expand DownExpand Up@@ -125,4 +133,9 @@ public function group($name, $label, $permissions = null)

$this->pendingGroup = null;
}

public function flattened()
{
return collect($this->permissions)->flatMap->flattened();
}
}
1 change: 0 additions & 1 deletion src/Http/Middleware/CP/Authorize.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,6 @@ public function handle($request, Closure $next)
}

if ($user->cant('access cp')) {
// dd('theres a user but they are unauthorized', $user);
throw new AuthorizationException('Unauthorized.');
}

Expand Down
4 changes: 3 additions & 1 deletion src/Policies/AssetContainerPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ class AssetContainerPolicy
{
public function before($user, $ability)
{
if (User::fromUser($user)->hasPermission('configure asset containers')) {
$user = User::fromUser($user);

if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/AssetFolderPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@

class AssetFolderPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function create($user, $assetContainer)
{
$user = User::fromUser($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/AssetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure asset containers')) {
if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/CollectionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/EntryPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FieldsetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability, $fieldset)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure fields')) {
if ($user->isSuper() || $user->hasPermission('configure fields')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormSubmissionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/GlobalSetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure globals')) {
if ($user->isSuper() || $user->hasPermission('configure globals')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/NavPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure navs')) {
if ($user->isSuper() || $user->hasPermission('configure navs')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/NavTreePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@ class NavTreePolicy extends NavPolicy
{
use Concerns\HasMultisitePolicy;

public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $nav)
{
$user = User::fromUser($user);
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/SitePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@

class SitePolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $site)
{
if (! Site::multiEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TaxonomyPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TermPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,13 @@

class UserPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function index($authed)
{
$authed = User::fromUser($authed);
Expand Down
20 changes: 15 additions & 5 deletions src/Providers/AuthServiceProvider.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@
use Statamic\Contracts\Auth\RoleRepository;
use Statamic\Contracts\Auth\UserGroupRepository;
use Statamic\Contracts\Auth\UserRepository;
use Statamic\Facades\Permission;
use Statamic\Facades\User;
use Statamic\Policies;

Expand DownExpand Up@@ -83,12 +84,21 @@ public function boot()
return new UserProvider;
});

Gate::before(function ($user, $ability) {
return optional(User::fromUser($user))->isSuper() ? true : null;
});

Gate::after(function ($user, $ability) {
return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null;
// If the ability isn't a Statamic permission, we don't want to get involved. 🙈
if (! Permission::boot()->flattened()->map->value()->contains($ability)) {
return null;
}

$user = User::fromUser($user);

if ($user->isSuper()) {
return true;
}

if ($user->hasPermission($ability)) {
return true;
}
});

foreach ($this->policies as $key => $policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/Sites/Site.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public function handle()

public function name()
{
return $this->config['name'];
return $this->config['name'] ?? $this->handle();
}

public function locale()
Expand Down
30 changes: 15 additions & 15 deletions tests/CP/Navigation/ActiveNavItemTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,8 +222,8 @@ public function it_can_check_if_parent_extension_with_array_based_children_item_
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -247,8 +247,8 @@ public function it_can_check_when_parent_and_array_based_child_extension_items_a
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -272,8 +272,8 @@ public function it_can_check_when_parent_and_array_based_descendant_of_child_ext
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -298,9 +298,9 @@ public function it_builds_extension_children_closure_when_not_active()
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/')->can('view seo reports'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults')->can('edit seo site defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -324,8 +324,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -351,8 +351,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -378,8 +378,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7aa69fa
Only perform super user check in `Gate::before()` when ability is a S…
duncanmcclean Feb 28, 2025
39a696e
Ensure permissions are booted before authorization is handled
duncanmcclean Feb 28, 2025
f34eafa
Remove commented out `dd()` from middleware 😄
duncanmcclean Feb 28, 2025
639a059
Add super user check to the `before` method in authorization policies.
duncanmcclean Feb 28, 2025
8fa1c6b
Ensure permissions exist, otherwise the super user check will fail.
duncanmcclean Feb 28, 2025
1b91834
Move the middleware back to where they were originally.
duncanmcclean Feb 28, 2025
7770675
Boot permissions in `Gate::before()` method...
duncanmcclean Feb 28, 2025
1995374
Update an existing test
duncanmcclean Feb 28, 2025
b365909
Fix super authorization in AssetFolderPolicy
duncanmcclean Feb 28, 2025
f23a4c9
Merge remote-tracking branch 'origin/master' into super-user-authoriz…
duncanmcclean Mar 6, 2025
3ce7705
Return early when permissions have already been booted.
duncanmcclean Mar 6, 2025
88e702f
Move super user check to `Gate::after()`
duncanmcclean Mar 7, 2025
d9b9567
Refactor super user / permission check
duncanmcclean Mar 7, 2025
1327752
Re-work the logic a little to allow for wildcard permissions
duncanmcclean Mar 7, 2025
ee19523
Register permission in test
duncanmcclean Mar 7, 2025
7abb312
Merge branch 'master' into super-user-authorization
jasonvarga Mar 11, 2025
de15f90
Test for booting once
jasonvarga Mar 11, 2025
f59a7e4
nitpick
jasonvarga Mar 11, 2025
0803113
Add gate tests
jasonvarga Mar 12, 2025
99d8acf
Add a flattened method and use that to check if a given permission is…
jasonvarga Mar 12, 2025
ca39b67
fix test failures ...
jasonvarga Mar 12, 2025
34d421c
These tests aren't concerned with permissions so just remove the cans…
jasonvarga Mar 13, 2025
705cc02
Test gate should not explicitly deny
jasonvarga Mar 13, 2025
fd7b7cb
User clearer names
jasonvarga Mar 13, 2025
2de3e31
Avoid even checking if user has permission if that ability is not a r…
jasonvarga Mar 13, 2025
c04820e
Bring back existing can() usage, but with an actual gate policy, sinc…
jasonvarga Mar 13, 2025
3e87ca5
Don't just register the successful one, and add a note.
jasonvarga Mar 13, 2025
691a0a8
Make chainable
jasonvarga Mar 13, 2025
91162f8
Explain
jasonvarga Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/Auth/Permission.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,50 @@ public function permissions()
})->values();
}

public function flattened()
{
if (! $this->callback) {
return [
$this,
...$this->children()->map(function ($child) {
return (new self)
->value($child->value())
->label($child->label())
->placeholder($this->placeholder)
->placeholderLabel($this->placeholderLabel)
->placeholderValue($this->placeholderValue)
->children($child->children()->all())
->group($this->group());
})->flatMap->flattened()->all(),
];
}

$items = call_user_func($this->callback);

return collect($items)->flatMap(function ($replacement) {
$replaced = (new self)
->value($this->value)
->label($this->label)
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->group($this->group());

$children = $this->children()->map(function ($child) use ($replacement) {
return (new self)
->value($child->originalValue())
->label($child->originalLabel())
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->children($child->children()->all())
->group($this->group());
});

return [$replaced, ...$children->flatMap->flattened()->all()];
})->values();
}

public function children(?array $children = null)
{
return $this
Expand Down
13 changes: 13 additions & 0 deletions src/Auth/Permissions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,14 @@ class Permissions
protected $permissions = [];
protected $groups = [];
protected $pendingGroup = null;
protected $booted = false;

public function boot()
{
if ($this->booted) {
return $this;
}

$early = $this->permissions;
$this->permissions = [];

Expand All@@ -23,6 +28,9 @@ public function boot()
}

$this->permissions = array_merge($this->permissions, $early);
$this->booted = true;

return $this;
}

public function extend($callback)
Expand DownExpand Up@@ -125,4 +133,9 @@ public function group($name, $label, $permissions = null)

$this->pendingGroup = null;
}

public function flattened()
{
return collect($this->permissions)->flatMap->flattened();
}
}
1 change: 0 additions & 1 deletion src/Http/Middleware/CP/Authorize.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,6 @@ public function handle($request, Closure $next)
}

if ($user->cant('access cp')) {
// dd('theres a user but they are unauthorized', $user);
throw new AuthorizationException('Unauthorized.');
}

Expand Down
4 changes: 3 additions & 1 deletion src/Policies/AssetContainerPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ class AssetContainerPolicy
{
public function before($user, $ability)
{
if (User::fromUser($user)->hasPermission('configure asset containers')) {
$user = User::fromUser($user);

if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/AssetFolderPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@

class AssetFolderPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function create($user, $assetContainer)
{
$user = User::fromUser($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/AssetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure asset containers')) {
if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/CollectionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/EntryPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FieldsetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability, $fieldset)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure fields')) {
if ($user->isSuper() || $user->hasPermission('configure fields')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormSubmissionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/GlobalSetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure globals')) {
if ($user->isSuper() || $user->hasPermission('configure globals')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/NavPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure navs')) {
if ($user->isSuper() || $user->hasPermission('configure navs')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/NavTreePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@ class NavTreePolicy extends NavPolicy
{
use Concerns\HasMultisitePolicy;

public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $nav)
{
$user = User::fromUser($user);
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/SitePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@

class SitePolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $site)
{
if (! Site::multiEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TaxonomyPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TermPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,13 @@

class UserPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function index($authed)
{
$authed = User::fromUser($authed);
Expand Down
20 changes: 15 additions & 5 deletions src/Providers/AuthServiceProvider.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@
use Statamic\Contracts\Auth\RoleRepository;
use Statamic\Contracts\Auth\UserGroupRepository;
use Statamic\Contracts\Auth\UserRepository;
use Statamic\Facades\Permission;
use Statamic\Facades\User;
use Statamic\Policies;

Expand DownExpand Up@@ -83,12 +84,21 @@ public function boot()
return new UserProvider;
});

Gate::before(function ($user, $ability) {
return optional(User::fromUser($user))->isSuper() ? true : null;
});

Gate::after(function ($user, $ability) {
return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null;
// If the ability isn't a Statamic permission, we don't want to get involved. 🙈
if (! Permission::boot()->flattened()->map->value()->contains($ability)) {
return null;
}

$user = User::fromUser($user);

if ($user->isSuper()) {
return true;
}

if ($user->hasPermission($ability)) {
return true;
}
});

foreach ($this->policies as $key => $policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/Sites/Site.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public function handle()

public function name()
{
return $this->config['name'];
return $this->config['name'] ?? $this->handle();
}

public function locale()
Expand Down
30 changes: 15 additions & 15 deletions tests/CP/Navigation/ActiveNavItemTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,8 +222,8 @@ public function it_can_check_if_parent_extension_with_array_based_children_item_
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -247,8 +247,8 @@ public function it_can_check_when_parent_and_array_based_child_extension_items_a
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -272,8 +272,8 @@ public function it_can_check_when_parent_and_array_based_descendant_of_child_ext
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -298,9 +298,9 @@ public function it_builds_extension_children_closure_when_not_active()
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/')->can('view seo reports'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults')->can('edit seo site defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -324,8 +324,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -351,8 +351,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -378,8 +378,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7aa69fa
Only perform super user check in `Gate::before()` when ability is a S…
duncanmcclean Feb 28, 2025
39a696e
Ensure permissions are booted before authorization is handled
duncanmcclean Feb 28, 2025
f34eafa
Remove commented out `dd()` from middleware 😄
duncanmcclean Feb 28, 2025
639a059
Add super user check to the `before` method in authorization policies.
duncanmcclean Feb 28, 2025
8fa1c6b
Ensure permissions exist, otherwise the super user check will fail.
duncanmcclean Feb 28, 2025
1b91834
Move the middleware back to where they were originally.
duncanmcclean Feb 28, 2025
7770675
Boot permissions in `Gate::before()` method...
duncanmcclean Feb 28, 2025
1995374
Update an existing test
duncanmcclean Feb 28, 2025
b365909
Fix super authorization in AssetFolderPolicy
duncanmcclean Feb 28, 2025
f23a4c9
Merge remote-tracking branch 'origin/master' into super-user-authoriz…
duncanmcclean Mar 6, 2025
3ce7705
Return early when permissions have already been booted.
duncanmcclean Mar 6, 2025
88e702f
Move super user check to `Gate::after()`
duncanmcclean Mar 7, 2025
d9b9567
Refactor super user / permission check
duncanmcclean Mar 7, 2025
1327752
Re-work the logic a little to allow for wildcard permissions
duncanmcclean Mar 7, 2025
ee19523
Register permission in test
duncanmcclean Mar 7, 2025
7abb312
Merge branch 'master' into super-user-authorization
jasonvarga Mar 11, 2025
de15f90
Test for booting once
jasonvarga Mar 11, 2025
f59a7e4
nitpick
jasonvarga Mar 11, 2025
0803113
Add gate tests
jasonvarga Mar 12, 2025
99d8acf
Add a flattened method and use that to check if a given permission is…
jasonvarga Mar 12, 2025
ca39b67
fix test failures ...
jasonvarga Mar 12, 2025
34d421c
These tests aren't concerned with permissions so just remove the cans…
jasonvarga Mar 13, 2025
705cc02
Test gate should not explicitly deny
jasonvarga Mar 13, 2025
fd7b7cb
User clearer names
jasonvarga Mar 13, 2025
2de3e31
Avoid even checking if user has permission if that ability is not a r…
jasonvarga Mar 13, 2025
c04820e
Bring back existing can() usage, but with an actual gate policy, sinc…
jasonvarga Mar 13, 2025
3e87ca5
Don't just register the successful one, and add a note.
jasonvarga Mar 13, 2025
691a0a8
Make chainable
jasonvarga Mar 13, 2025
91162f8
Explain
jasonvarga Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/Auth/Permission.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,50 @@ public function permissions()
})->values();
}

public function flattened()
{
if (! $this->callback) {
return [
$this,
...$this->children()->map(function ($child) {
return (new self)
->value($child->value())
->label($child->label())
->placeholder($this->placeholder)
->placeholderLabel($this->placeholderLabel)
->placeholderValue($this->placeholderValue)
->children($child->children()->all())
->group($this->group());
})->flatMap->flattened()->all(),
];
}

$items = call_user_func($this->callback);

return collect($items)->flatMap(function ($replacement) {
$replaced = (new self)
->value($this->value)
->label($this->label)
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->group($this->group());

$children = $this->children()->map(function ($child) use ($replacement) {
return (new self)
->value($child->originalValue())
->label($child->originalLabel())
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->children($child->children()->all())
->group($this->group());
});

return [$replaced, ...$children->flatMap->flattened()->all()];
})->values();
}

public function children(?array $children = null)
{
return $this
Expand Down
13 changes: 13 additions & 0 deletions src/Auth/Permissions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,14 @@ class Permissions
protected $permissions = [];
protected $groups = [];
protected $pendingGroup = null;
protected $booted = false;

public function boot()
{
if ($this->booted) {
return $this;
}

$early = $this->permissions;
$this->permissions = [];

Expand All@@ -23,6 +28,9 @@ public function boot()
}

$this->permissions = array_merge($this->permissions, $early);
$this->booted = true;

return $this;
}

public function extend($callback)
Expand DownExpand Up@@ -125,4 +133,9 @@ public function group($name, $label, $permissions = null)

$this->pendingGroup = null;
}

public function flattened()
{
return collect($this->permissions)->flatMap->flattened();
}
}
1 change: 0 additions & 1 deletion src/Http/Middleware/CP/Authorize.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,6 @@ public function handle($request, Closure $next)
}

if ($user->cant('access cp')) {
// dd('theres a user but they are unauthorized', $user);
throw new AuthorizationException('Unauthorized.');
}

Expand Down
4 changes: 3 additions & 1 deletion src/Policies/AssetContainerPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ class AssetContainerPolicy
{
public function before($user, $ability)
{
if (User::fromUser($user)->hasPermission('configure asset containers')) {
$user = User::fromUser($user);

if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/AssetFolderPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@

class AssetFolderPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function create($user, $assetContainer)
{
$user = User::fromUser($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/AssetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure asset containers')) {
if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/CollectionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/EntryPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FieldsetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability, $fieldset)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure fields')) {
if ($user->isSuper() || $user->hasPermission('configure fields')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormSubmissionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/GlobalSetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure globals')) {
if ($user->isSuper() || $user->hasPermission('configure globals')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/NavPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure navs')) {
if ($user->isSuper() || $user->hasPermission('configure navs')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/NavTreePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@ class NavTreePolicy extends NavPolicy
{
use Concerns\HasMultisitePolicy;

public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $nav)
{
$user = User::fromUser($user);
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/SitePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@

class SitePolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $site)
{
if (! Site::multiEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TaxonomyPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TermPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,13 @@

class UserPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function index($authed)
{
$authed = User::fromUser($authed);
Expand Down
20 changes: 15 additions & 5 deletions src/Providers/AuthServiceProvider.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@
use Statamic\Contracts\Auth\RoleRepository;
use Statamic\Contracts\Auth\UserGroupRepository;
use Statamic\Contracts\Auth\UserRepository;
use Statamic\Facades\Permission;
use Statamic\Facades\User;
use Statamic\Policies;

Expand DownExpand Up@@ -83,12 +84,21 @@ public function boot()
return new UserProvider;
});

Gate::before(function ($user, $ability) {
return optional(User::fromUser($user))->isSuper() ? true : null;
});

Gate::after(function ($user, $ability) {
return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null;
// If the ability isn't a Statamic permission, we don't want to get involved. 🙈
if (! Permission::boot()->flattened()->map->value()->contains($ability)) {
return null;
}

$user = User::fromUser($user);

if ($user->isSuper()) {
return true;
}

if ($user->hasPermission($ability)) {
return true;
}
});

foreach ($this->policies as $key => $policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/Sites/Site.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public function handle()

public function name()
{
return $this->config['name'];
return $this->config['name'] ?? $this->handle();
}

public function locale()
Expand Down
30 changes: 15 additions & 15 deletions tests/CP/Navigation/ActiveNavItemTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,8 +222,8 @@ public function it_can_check_if_parent_extension_with_array_based_children_item_
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -247,8 +247,8 @@ public function it_can_check_when_parent_and_array_based_child_extension_items_a
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -272,8 +272,8 @@ public function it_can_check_when_parent_and_array_based_descendant_of_child_ext
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -298,9 +298,9 @@ public function it_builds_extension_children_closure_when_not_active()
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/')->can('view seo reports'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults')->can('edit seo site defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -324,8 +324,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -351,8 +351,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -378,8 +378,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7aa69fa
Only perform super user check in `Gate::before()` when ability is a S…
duncanmcclean Feb 28, 2025
39a696e
Ensure permissions are booted before authorization is handled
duncanmcclean Feb 28, 2025
f34eafa
Remove commented out `dd()` from middleware 😄
duncanmcclean Feb 28, 2025
639a059
Add super user check to the `before` method in authorization policies.
duncanmcclean Feb 28, 2025
8fa1c6b
Ensure permissions exist, otherwise the super user check will fail.
duncanmcclean Feb 28, 2025
1b91834
Move the middleware back to where they were originally.
duncanmcclean Feb 28, 2025
7770675
Boot permissions in `Gate::before()` method...
duncanmcclean Feb 28, 2025
1995374
Update an existing test
duncanmcclean Feb 28, 2025
b365909
Fix super authorization in AssetFolderPolicy
duncanmcclean Feb 28, 2025
f23a4c9
Merge remote-tracking branch 'origin/master' into super-user-authoriz…
duncanmcclean Mar 6, 2025
3ce7705
Return early when permissions have already been booted.
duncanmcclean Mar 6, 2025
88e702f
Move super user check to `Gate::after()`
duncanmcclean Mar 7, 2025
d9b9567
Refactor super user / permission check
duncanmcclean Mar 7, 2025
1327752
Re-work the logic a little to allow for wildcard permissions
duncanmcclean Mar 7, 2025
ee19523
Register permission in test
duncanmcclean Mar 7, 2025
7abb312
Merge branch 'master' into super-user-authorization
jasonvarga Mar 11, 2025
de15f90
Test for booting once
jasonvarga Mar 11, 2025
f59a7e4
nitpick
jasonvarga Mar 11, 2025
0803113
Add gate tests
jasonvarga Mar 12, 2025
99d8acf
Add a flattened method and use that to check if a given permission is…
jasonvarga Mar 12, 2025
ca39b67
fix test failures ...
jasonvarga Mar 12, 2025
34d421c
These tests aren't concerned with permissions so just remove the cans…
jasonvarga Mar 13, 2025
705cc02
Test gate should not explicitly deny
jasonvarga Mar 13, 2025
fd7b7cb
User clearer names
jasonvarga Mar 13, 2025
2de3e31
Avoid even checking if user has permission if that ability is not a r…
jasonvarga Mar 13, 2025
c04820e
Bring back existing can() usage, but with an actual gate policy, sinc…
jasonvarga Mar 13, 2025
3e87ca5
Don't just register the successful one, and add a note.
jasonvarga Mar 13, 2025
691a0a8
Make chainable
jasonvarga Mar 13, 2025
91162f8
Explain
jasonvarga Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/Auth/Permission.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,50 @@ public function permissions()
})->values();
}

public function flattened()
{
if (! $this->callback) {
return [
$this,
...$this->children()->map(function ($child) {
return (new self)
->value($child->value())
->label($child->label())
->placeholder($this->placeholder)
->placeholderLabel($this->placeholderLabel)
->placeholderValue($this->placeholderValue)
->children($child->children()->all())
->group($this->group());
})->flatMap->flattened()->all(),
];
}

$items = call_user_func($this->callback);

return collect($items)->flatMap(function ($replacement) {
$replaced = (new self)
->value($this->value)
->label($this->label)
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->group($this->group());

$children = $this->children()->map(function ($child) use ($replacement) {
return (new self)
->value($child->originalValue())
->label($child->originalLabel())
->placeholder($this->placeholder)
->placeholderLabel($replacement['label'])
->placeholderValue($replacement['value'])
->children($child->children()->all())
->group($this->group());
});

return [$replaced, ...$children->flatMap->flattened()->all()];
})->values();
}

public function children(?array $children = null)
{
return $this
Expand Down
13 changes: 13 additions & 0 deletions src/Auth/Permissions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,9 +10,14 @@ class Permissions
protected $permissions = [];
protected $groups = [];
protected $pendingGroup = null;
protected $booted = false;

public function boot()
{
if ($this->booted) {
return $this;
}

$early = $this->permissions;
$this->permissions = [];

Expand All@@ -23,6 +28,9 @@ public function boot()
}

$this->permissions = array_merge($this->permissions, $early);
$this->booted = true;

return $this;
}

public function extend($callback)
Expand DownExpand Up@@ -125,4 +133,9 @@ public function group($name, $label, $permissions = null)

$this->pendingGroup = null;
}

public function flattened()
{
return collect($this->permissions)->flatMap->flattened();
}
}
1 change: 0 additions & 1 deletion src/Http/Middleware/CP/Authorize.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,6 @@ public function handle($request, Closure $next)
}

if ($user->cant('access cp')) {
// dd('theres a user but they are unauthorized', $user);
throw new AuthorizationException('Unauthorized.');
}

Expand Down
4 changes: 3 additions & 1 deletion src/Policies/AssetContainerPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,9 @@ class AssetContainerPolicy
{
public function before($user, $ability)
{
if (User::fromUser($user)->hasPermission('configure asset containers')) {
$user = User::fromUser($user);

if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/AssetFolderPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@

class AssetFolderPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function create($user, $assetContainer)
{
$user = User::fromUser($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/AssetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure asset containers')) {
if ($user->isSuper() || $user->hasPermission('configure asset containers')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/CollectionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/EntryPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure collections')) {
if ($user->isSuper() || $user->hasPermission('configure collections')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FieldsetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability, $fieldset)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure fields')) {
if ($user->isSuper() || $user->hasPermission('configure fields')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/FormSubmissionPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ public function before($user, $ability)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure forms')) {
if ($user->isSuper() || $user->hasPermission('configure forms')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/GlobalSetPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure globals')) {
if ($user->isSuper() || $user->hasPermission('configure globals')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/NavPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure navs')) {
if ($user->isSuper() || $user->hasPermission('configure navs')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/NavTreePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,13 @@ class NavTreePolicy extends NavPolicy
{
use Concerns\HasMultisitePolicy;

public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $nav)
{
$user = User::fromUser($user);
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/SitePolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@

class SitePolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function view($user, $site)
{
if (! Site::multiEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TaxonomyPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Policies/TermPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ public function before($user)
{
$user = User::fromUser($user);

if ($user->hasPermission('configure taxonomies')) {
if ($user->isSuper() || $user->hasPermission('configure taxonomies')) {
return true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Policies/UserPolicy.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,13 @@

class UserPolicy
{
public function before($user)
{
if (User::fromUser($user)->isSuper()) {
return true;
}
}

public function index($authed)
{
$authed = User::fromUser($authed);
Expand Down
20 changes: 15 additions & 5 deletions src/Providers/AuthServiceProvider.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@
use Statamic\Contracts\Auth\RoleRepository;
use Statamic\Contracts\Auth\UserGroupRepository;
use Statamic\Contracts\Auth\UserRepository;
use Statamic\Facades\Permission;
use Statamic\Facades\User;
use Statamic\Policies;

Expand DownExpand Up@@ -83,12 +84,21 @@ public function boot()
return new UserProvider;
});

Gate::before(function ($user, $ability) {
return optional(User::fromUser($user))->isSuper() ? true : null;
});

Gate::after(function ($user, $ability) {
return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null;
// If the ability isn't a Statamic permission, we don't want to get involved. 🙈
if (! Permission::boot()->flattened()->map->value()->contains($ability)) {
return null;
}

$user = User::fromUser($user);

if ($user->isSuper()) {
return true;
}

if ($user->hasPermission($ability)) {
return true;
}
});

foreach ($this->policies as $key => $policy) {
Expand Down
2 changes: 1 addition & 1 deletion src/Sites/Site.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public function handle()

public function name()
{
return $this->config['name'];
return $this->config['name'] ?? $this->handle();
}

public function locale()
Expand Down
30 changes: 15 additions & 15 deletions tests/CP/Navigation/ActiveNavItemTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,8 +222,8 @@ public function it_can_check_if_parent_extension_with_array_based_children_item_
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -247,8 +247,8 @@ public function it_can_check_when_parent_and_array_based_child_extension_items_a
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -272,8 +272,8 @@ public function it_can_check_when_parent_and_array_based_descendant_of_child_ext
$nav->tools('SEO Pro')
->url('/cp/seo-pro')
->children([
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
]);
});

Expand All@@ -298,9 +298,9 @@ public function it_builds_extension_children_closure_when_not_active()
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/')->can('view seo reports'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults')->can('edit seo site defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/'),
$nav->item('Site Defaults')->url('/cp/seo-pro/site-defaults'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -324,8 +324,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -351,8 +351,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand All@@ -378,8 +378,8 @@ public function it_resolves_extension_children_closure_and_can_check_when_parent
->url('/cp/seo-pro')
->children(function () use ($nav) {
return [
$nav->item('Reports')->url('/cp/seo-pro/reports')->can('view seo reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults')->can('edit seo section defaults'),
$nav->item('Reports')->url('/cp/seo-pro/reports'),
$nav->item('Section Defaults')->url('/cp/seo-pro/section-defaults'),
];
});
});
Expand Down
Loading