feat(1.13.0): page grants on the standard admissions-style pattern
unifi_page_grants gains role + default grantee types and can_view (deny-by-default "Everyone else" row); enforcement moves from the RouteMatched listener — where request->user() is always null and the check silently failed open — into route-appended middleware with the permission-holder pass. Pages-access endpoints gain role search + default-row handling. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -13,11 +13,16 @@ class UnifiPageGrant extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'nav_item_id',
|
||||
'grantee_type',
|
||||
'grantee_id',
|
||||
'grantee_type', // default | user | group | role
|
||||
'grantee_id', // null for grantee_type=default
|
||||
'can_view',
|
||||
'granted_by_user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'can_view' => 'boolean',
|
||||
];
|
||||
|
||||
public function navItem(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(NavItem::class);
|
||||
@@ -29,30 +34,32 @@ class UnifiPageGrant extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* True iff $user is allowed to access $navItem under strict allowlist
|
||||
* semantics:
|
||||
* * super-admins (the model-level flag) always pass
|
||||
* * otherwise the user must be a direct grantee, OR be in a group
|
||||
* that is a grantee
|
||||
*
|
||||
* A page with NO grants saved is only visible to super-admins —
|
||||
* the admin must explicitly authorize everyone else via the
|
||||
* Access tab.
|
||||
* Grant check with a per-page default fallback. Passes if the user
|
||||
* matches an explicit user/group/role grant; otherwise falls back
|
||||
* to the page's `default` row (deny by default — no row or
|
||||
* can_view=false denies). Permission-based access is checked
|
||||
* separately by the caller — grants extend, never revoke.
|
||||
*/
|
||||
public static function userCanAccess(User $user, NavItem $navItem): bool
|
||||
{
|
||||
if ($user->is_super_admin) return true;
|
||||
|
||||
$groupIds = $user->groups()->pluck('groups.id');
|
||||
$roleIds = $user->roles()->pluck('roles.id');
|
||||
|
||||
return static::where('nav_item_id', $navItem->id)
|
||||
->where(function ($q) use ($user, $groupIds) {
|
||||
$q->where(function ($u) use ($user) {
|
||||
$u->where('grantee_type', 'user')->where('grantee_id', $user->id);
|
||||
})->orWhere(function ($g) use ($groupIds) {
|
||||
$g->where('grantee_type', 'group')->whereIn('grantee_id', $groupIds);
|
||||
});
|
||||
$hasExplicit = static::where('nav_item_id', $navItem->id)
|
||||
->where('can_view', true)
|
||||
->where(function ($q) use ($user, $groupIds, $roleIds) {
|
||||
$q->where(fn ($u) => $u->where('grantee_type', 'user')->where('grantee_id', $user->id))
|
||||
->orWhere(fn ($g) => $g->where('grantee_type', 'group')->whereIn('grantee_id', $groupIds))
|
||||
->orWhere(fn ($r) => $r->where('grantee_type', 'role')->whereIn('grantee_id', $roleIds));
|
||||
})
|
||||
->exists();
|
||||
|
||||
if ($hasExplicit) return true;
|
||||
|
||||
return (bool) static::where('nav_item_id', $navItem->id)
|
||||
->where('grantee_type', 'default')
|
||||
->value('can_view');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user