Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 15 additions & 9 deletions src/Models/Roster.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,6 @@ final class Roster extends Model

public $timestamps = true;

protected $fillable = [
'assignable_type',
'assignable_id',
'roleable_type',
'roleable_id',
'role_key',
'tenant_id',
];

protected $casts = [
'role_key' => RoleCast::class,
];
Expand All @@ -77,6 +68,21 @@ public function __construct(array $attributes = [])
$this->conditionallyLoadRelations();
}

/**
* @return string[]
*/
public function getFillable(): array
{
return [
'assignable_type',
'assignable_id',
'roleable_type',
'roleable_id',
'role_key',
config('porter.multitenancy.tenant_column', 'tenant_id'),
];
}

/**
* Conditionally load relations based on configuration and environment.
*/
Expand Down
50 changes: 31 additions & 19 deletions src/RoleManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function assign(AssignableEntity $user, RoleableEntity $target, string|Ro
$assignmentData[$tenantColumn] = $tenantId;
}

$assignment = Roster::firstOrCreate($assignmentData);
$assignment = $this->resolveRosterModel()::firstOrCreate($assignmentData);

if ($assignment->wasRecentlyCreated) {
RoleAssigned::dispatch($user, $target, $roleInstance);
Expand Down Expand Up @@ -141,7 +141,7 @@ public function getParticipantsHasRole(RoleableEntity $target, string|RoleContra
$roleInstance = $this->resolveRole($role);
$encryptedKey = $roleInstance::getDbKey();

return Roster::where([
return $this->resolveRosterModel()::where([
'roleable_type' => $target->getMorphClass(),
'roleable_id' => $target->getKey(),
'role_key' => $encryptedKey,
Expand All @@ -158,7 +158,7 @@ public function getParticipantsHasRole(RoleableEntity $target, string|RoleContra
*/
public function getAssignedEntitiesByKeysByType(AssignableEntity $target, array $keys, string $type): Collection
{
return Roster::query()->where([
return $this->resolveRosterModel()::query()->where([
'assignable_type' => $target->getMorphClass(),
'assignable_id' => $target->getKey(),
'roleable_type' => $type,
Expand All @@ -180,7 +180,7 @@ public function getAssignedEntitiesByKeysByType(AssignableEntity $target, array
public function getAssignedEntitiesByType(AssignableEntity $entity, string $type): Collection
{
if (! RoleCacheManager::isEnabled()) {
return Roster::where([
return $this->resolveRosterModel()::where([
'roleable_type' => $type,
'assignable_type' => $entity->getMorphClass(),
'assignable_id' => $entity->getKey(),
Expand All @@ -191,11 +191,14 @@ public function getAssignedEntitiesByType(AssignableEntity $entity, string $type

$cacheKey = RoleCacheManager::generateAssignedEntitiesCacheKey($entity, $type);

return RoleCacheManager::remember($cacheKey, RoleCacheManager::getTtl('assigned_entities'), fn () => Roster::where([
'roleable_type' => $type,
'assignable_type' => $entity->getMorphClass(),
'assignable_id' => $entity->getKey(),
])
return RoleCacheManager::remember(
$cacheKey,
RoleCacheManager::getTtl('assigned_entities'),
fn () => $this->resolveRosterModel()::where([
'roleable_type' => $type,
'assignable_type' => $entity->getMorphClass(),
'assignable_id' => $entity->getKey(),
])
->with('roleable')
->get()
->pluck('roleable'));
Expand All @@ -211,7 +214,7 @@ public function getAssignedEntitiesByType(AssignableEntity $entity, string $type
public function getParticipantsWithRoles(RoleableEntity $target): Collection
{
if (! RoleCacheManager::isEnabled()) {
return Roster::where([
return $this->resolveRosterModel()::where([
'roleable_id' => $target->getKey(),
'roleable_type' => $target->getMorphClass(),
])
Expand All @@ -222,7 +225,7 @@ public function getParticipantsWithRoles(RoleableEntity $target): Collection
return RoleCacheManager::remember(
RoleCacheManager::generateParticipantsCacheKey($target),
RoleCacheManager::getTtl('participants'),
fn () => Roster::where([
fn () => $this->resolveRosterModel()::where([
'roleable_id' => $target->getKey(),
'roleable_type' => $target->getMorphClass(),
])
Expand Down Expand Up @@ -268,7 +271,7 @@ public function hasRoleOn(AssignableEntity $user, RoleableEntity $target, string
*/
public function hasAnyRoleOn(AssignableEntity $user, RoleableEntity $target): bool
{
return Roster::where([
return $this->resolveRosterModel()::where([
'assignable_id' => $user->getKey(),
'assignable_type' => $user->getMorphClass(),
'roleable_id' => $target->getKey(),
Expand All @@ -285,7 +288,7 @@ public function hasAnyRoleOn(AssignableEntity $user, RoleableEntity $target): bo
*/
public function getRoleOn(AssignableEntity $user, RoleableEntity $target): ?RoleContract
{
$roster = Roster::where([
$roster = $this->resolveRosterModel()::where([
'assignable_id' => $user->getKey(),
'assignable_type' => $user->getMorphClass(),
'roleable_type' => $target->getMorphClass(),
Expand Down Expand Up @@ -381,7 +384,7 @@ public function bulkClearCache(Collection $targets): void
*/
private function removeWithinTransaction(AssignableEntity $user, RoleableEntity $target): void
{
$assignments = Roster::where([
$assignments = $this->resolveRosterModel()::where([
'assignable_type' => $user->getMorphClass(),
'assignable_id' => $user->getKey(),
'roleable_id' => $target->getKey(),
Expand All @@ -396,7 +399,7 @@ private function removeWithinTransaction(AssignableEntity $user, RoleableEntity
}

$assignmentIds = $assignments->pluck('id');
Roster::whereIn('id', $assignmentIds)->delete();
$this->resolveRosterModel()::whereIn('id', $assignmentIds)->delete();

foreach ($assignments as $assignment) {
$encryptedKey = $assignment->getRoleDBKey();
Expand Down Expand Up @@ -441,7 +444,7 @@ private function performRoleCheck(AssignableEntity $user, RoleableEntity $target
private function executeRoleCheck(AssignableEntity $user, RoleableEntity $target, string $encryptedKey): bool
{
// First try exact match (most common case)
$exists = Roster::where([
$exists = $this->resolveRosterModel()::where([
'assignable_id' => $user->getKey(),
'assignable_type' => $user->getMorphClass(),
'roleable_id' => $target->getKey(),
Expand All @@ -464,7 +467,7 @@ private function executeRoleCheck(AssignableEntity $user, RoleableEntity $target
$plainKey = $role::getPlainKey();

// Check if there's a record with the plain text key
$exists = Roster::where([
$exists = $this->resolveRosterModel()::where([
'assignable_id' => $user->getKey(),
'assignable_type' => $user->getMorphClass(),
'roleable_id' => $target->getKey(),
Expand Down Expand Up @@ -541,7 +544,7 @@ private function validateTenantIntegrity(AssignableEntity $user, RoleableEntity
// Allow if user already has any role in the target's tenant (existing tenant participant)
if ($roleableTenant !== null) {
$tenantColumn = config('porter.multitenancy.tenant_column', 'tenant_id');
$hasRoleInTenant = Roster::where([
$hasRoleInTenant = $this->resolveRosterModel()::where([
'assignable_type' => $user->getMorphClass(),
'assignable_id' => $user->getKey(),
])->where($tenantColumn, $roleableTenant)->exists();
Expand Down Expand Up @@ -589,6 +592,15 @@ private function resolveTenantIdForAssignment(AssignableEntity $user, RoleableEn
return $user instanceof PorterAssignableContract ? $user->getPorterCurrentTenantKey() : null;
}

/**
* @return class-string
*/

private function resolveRosterModel(): string
{
return config('porter.models.roster', Roster::class);
}

/**
* Destroy all role assignments for a specific tenant.
* Cache will self-heal as stale entries expire and new queries return correct results.
Expand All @@ -605,6 +617,6 @@ public function destroyTenantRoles(string $tenantKey): bool

$tenantColumn = config('porter.multitenancy.tenant_column', 'tenant_id');

return Roster::where($tenantColumn, $tenantKey)->delete() > 0;
return $this->resolveRosterModel()::where($tenantColumn, $tenantKey)->delete() > 0;
}
}