Refactor api key permissions (#361)

* use RESOURCE_NAME for requests

* use RESOURCE_NAME for transformers

* add permissions field to api key

* add migration for new permissions field

* update tests

* remove debug log

* set column type to "json"

* remove default attribute to fix tests

* fix default value for permissions

* fix after merge

* fix after merge

* allow to "register" custom permissions

* add "role" to default resource names

* fix after merge

* fix phpstan

* fix migrations
This commit is contained in:
Boy132 2024-11-06 09:09:10 +01:00 committed by GitHub
parent ac67656d82
commit b3501be6ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
60 changed files with 453 additions and 321 deletions

View File

@ -11,6 +11,7 @@ use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Illuminate\Database\Eloquent\Model;
class CreateApiKey extends CreateRecord class CreateApiKey extends CreateRecord
{ {
@ -41,7 +42,7 @@ class CreateApiKey extends CreateRecord
'md' => 2, 'md' => 2,
]) ])
->schema( ->schema(
collect(ApiKey::RESOURCES)->map(fn ($resource) => ToggleButtons::make("r_$resource") collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
->label(str($resource)->replace('_', ' ')->title())->inline() ->label(str($resource)->replace('_', ' ')->title())->inline()
->options([ ->options([
0 => 'None', 0 => 'None',
@ -87,4 +88,20 @@ class CreateApiKey extends CreateRecord
->columnSpanFull(), ->columnSpanFull(),
]); ]);
} }
protected function handleRecordCreation(array $data): Model
{
$permissions = [];
foreach (ApiKey::getPermissionList() as $permission) {
if (isset($data['permissions_' . $permission])) {
$permissions[$permission] = intval($data['permissions_' . $permission]);
unset($data['permissions_' . $permission]);
}
}
$data['permissions'] = $permissions;
return parent::handleRecordCreation($data);
}
} }

View File

@ -3,7 +3,6 @@
namespace App\Http\Requests\Admin\Api; namespace App\Http\Requests\Admin\Api;
use App\Models\ApiKey; use App\Models\ApiKey;
use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Admin\AdminFormRequest; use App\Http\Requests\Admin\AdminFormRequest;
class StoreApplicationApiKeyRequest extends AdminFormRequest class StoreApplicationApiKeyRequest extends AdminFormRequest
@ -16,9 +15,12 @@ class StoreApplicationApiKeyRequest extends AdminFormRequest
{ {
$modelRules = ApiKey::getRules(); $modelRules = ApiKey::getRules();
return collect(AdminAcl::getResourceList())->mapWithKeys(function ($resource) use ($modelRules) { $rules = [
return [AdminAcl::COLUMN_IDENTIFIER . $resource => $modelRules['r_' . $resource]]; 'memo' => $modelRules['memo'],
})->merge(['memo' => $modelRules['memo']])->toArray(); 'permissions' => $modelRules['permissions'],
];
return $rules;
} }
public function attributes(): array public function attributes(): array
@ -30,8 +32,8 @@ class StoreApplicationApiKeyRequest extends AdminFormRequest
public function getKeyPermissions(): array public function getKeyPermissions(): array
{ {
return collect($this->validated())->filter(function ($value, $key) { $data = $this->validated();
return substr($key, 0, strlen(AdminAcl::COLUMN_IDENTIFIER)) === AdminAcl::COLUMN_IDENTIFIER;
})->toArray(); return array_keys($data['permissions']);
} }
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Allocations;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Allocation;
class DeleteAllocationRequest extends ApplicationApiRequest class DeleteAllocationRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; protected ?string $resource = Allocation::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Allocations;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Allocation;
class GetAllocationsRequest extends ApplicationApiRequest class GetAllocationsRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; protected ?string $resource = Allocation::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Allocations;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Allocation;
class StoreAllocationRequest extends ApplicationApiRequest class StoreAllocationRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; protected ?string $resource = Allocation::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\DatabaseHosts;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\DatabaseHost;
class DeleteDatabaseHostRequest extends ApplicationApiRequest class DeleteDatabaseHostRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_DATABASE_HOSTS; protected ?string $resource = DatabaseHost::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\DatabaseHosts;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\DatabaseHost;
class GetDatabaseHostRequest extends ApplicationApiRequest class GetDatabaseHostRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_DATABASE_HOSTS; protected ?string $resource = DatabaseHost::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -8,7 +8,7 @@ use App\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreDatabaseHostRequest extends ApplicationApiRequest class StoreDatabaseHostRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_DATABASE_HOSTS; protected ?string $resource = DatabaseHost::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Eggs;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Models\Egg;
class GetEggRequest extends ApplicationApiRequest class GetEggRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_EGGS; protected ?string $resource = Egg::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Eggs;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Models\Egg;
class GetEggsRequest extends ApplicationApiRequest class GetEggsRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_EGGS; protected ?string $resource = Egg::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Mounts;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Mount;
class DeleteMountRequest extends ApplicationApiRequest class DeleteMountRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_MOUNTS; protected ?string $resource = Mount::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Mounts;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Mount;
class GetMountRequest extends ApplicationApiRequest class GetMountRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_MOUNTS; protected ?string $resource = Mount::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Mounts;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Mount;
class StoreMountRequest extends ApplicationApiRequest class StoreMountRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_MOUNTS; protected ?string $resource = Mount::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Nodes;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Node;
class DeleteNodeRequest extends ApplicationApiRequest class DeleteNodeRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_NODES; protected ?string $resource = Node::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Nodes;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Node;
class GetNodesRequest extends ApplicationApiRequest class GetNodesRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_NODES; protected ?string $resource = Node::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -8,7 +8,7 @@ use App\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreNodeRequest extends ApplicationApiRequest class StoreNodeRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_NODES; protected ?string $resource = Node::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Roles;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Role;
class DeleteRoleRequest extends ApplicationApiRequest class DeleteRoleRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_ROLES; protected ?string $resource = Role::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Roles;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Role;
class GetRoleRequest extends ApplicationApiRequest class GetRoleRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_ROLES; protected ?string $resource = Role::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Roles;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Role;
class StoreRoleRequest extends ApplicationApiRequest class StoreRoleRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_ROLES; protected ?string $resource = Role::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Servers\Databases;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Database;
class GetServerDatabaseRequest extends ApplicationApiRequest class GetServerDatabaseRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; protected ?string $resource = Database::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Servers\Databases;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Database;
class GetServerDatabasesRequest extends ApplicationApiRequest class GetServerDatabasesRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; protected ?string $resource = Database::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -9,10 +9,11 @@ use Illuminate\Database\Query\Builder;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DatabaseManagementService;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Database;
class StoreServerDatabaseRequest extends ApplicationApiRequest class StoreServerDatabaseRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; protected ?string $resource = Database::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Servers;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Server;
class GetExternalServerRequest extends ApplicationApiRequest class GetExternalServerRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_SERVERS; protected ?string $resource = Server::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Servers;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Server;
class GetServerRequest extends ApplicationApiRequest class GetServerRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_SERVERS; protected ?string $resource = Server::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Servers;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Server;
class ServerWriteRequest extends ApplicationApiRequest class ServerWriteRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_SERVERS; protected ?string $resource = Server::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
} }

View File

@ -11,7 +11,7 @@ use App\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreServerRequest extends ApplicationApiRequest class StoreServerRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_SERVERS; protected ?string $resource = Server::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;

View File

@ -8,7 +8,7 @@ use App\Http\Requests\Api\Application\ApplicationApiRequest;
class UpdateServerStartupRequest extends ApplicationApiRequest class UpdateServerStartupRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_SERVERS; protected ?string $resource = Server::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Users;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\User;
class DeleteUserRequest extends ApplicationApiRequest class DeleteUserRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_USERS; protected ?string $resource = User::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Users;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\User;
class GetExternalUserRequest extends ApplicationApiRequest class GetExternalUserRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_USERS; protected ?string $resource = User::RESOURCE_NAME;
protected int $permission = AdminAcl::READ; protected int $permission = AdminAcl::READ;
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Requests\Api\Application\Users;
use App\Services\Acl\Api\AdminAcl as Acl; use App\Services\Acl\Api\AdminAcl as Acl;
use App\Http\Requests\Api\Application\ApplicationApiRequest; use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\User;
class GetUsersRequest extends ApplicationApiRequest class GetUsersRequest extends ApplicationApiRequest
{ {
protected ?string $resource = Acl::RESOURCE_USERS; protected ?string $resource = User::RESOURCE_NAME;
protected int $permission = Acl::READ; protected int $permission = Acl::READ;
} }

View File

@ -8,7 +8,7 @@ use App\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreUserRequest extends ApplicationApiRequest class StoreUserRequest extends ApplicationApiRequest
{ {
protected ?string $resource = AdminAcl::RESOURCE_USERS; protected ?string $resource = User::RESOURCE_NAME;
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;

View File

@ -43,7 +43,7 @@ class Allocation extends Model
{ {
/** /**
* The resource name for this model when it is transformed into an * The resource name for this model when it is transformed into an
* API representation using fractal. * API representation using fractal. Also used as name for api key permissions.
*/ */
public const RESOURCE_NAME = 'allocation'; public const RESOURCE_NAME = 'allocation';

View File

@ -2,9 +2,9 @@
namespace App\Models; namespace App\Models;
use App\Services\Acl\Api\AdminAcl;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use App\Services\Acl\Api\AdminAcl;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
/** /**
@ -15,20 +15,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property int $key_type * @property int $key_type
* @property string $identifier * @property string $identifier
* @property string $token * @property string $token
* @property array $permissions
* @property array $allowed_ips * @property array $allowed_ips
* @property string|null $memo * @property string|null $memo
* @property \Illuminate\Support\Carbon|null $last_used_at * @property \Illuminate\Support\Carbon|null $last_used_at
* @property \Illuminate\Support\Carbon|null $expires_at * @property \Illuminate\Support\Carbon|null $expires_at
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property int $r_servers
* @property int $r_nodes
* @property int $r_allocations
* @property int $r_users
* @property int $r_eggs
* @property int $r_database_hosts
* @property int $r_server_databases
* @property int $r_mounts
* @property \App\Models\User $tokenable * @property \App\Models\User $tokenable
* @property \App\Models\User $user * @property \App\Models\User $user
* *
@ -84,8 +77,6 @@ class ApiKey extends Model
*/ */
public const KEY_LENGTH = 32; public const KEY_LENGTH = 32;
public const RESOURCES = ['servers', 'nodes', 'allocations', 'users', 'eggs', 'database_hosts', 'server_databases', 'mounts'];
/** /**
* The table associated with the model. * The table associated with the model.
*/ */
@ -99,18 +90,11 @@ class ApiKey extends Model
'key_type', 'key_type',
'identifier', 'identifier',
'token', 'token',
'permissions',
'allowed_ips', 'allowed_ips',
'memo', 'memo',
'last_used_at', 'last_used_at',
'expires_at', 'expires_at',
'r_' . AdminAcl::RESOURCE_USERS,
'r_' . AdminAcl::RESOURCE_ALLOCATIONS,
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS,
'r_' . AdminAcl::RESOURCE_SERVER_DATABASES,
'r_' . AdminAcl::RESOURCE_EGGS,
'r_' . AdminAcl::RESOURCE_NODES,
'r_' . AdminAcl::RESOURCE_SERVERS,
'r_' . AdminAcl::RESOURCE_MOUNTS,
]; ];
/** /**
@ -118,6 +102,7 @@ class ApiKey extends Model
*/ */
protected $attributes = [ protected $attributes = [
'allowed_ips' => '[]', 'allowed_ips' => '[]',
'permissions' => '[]',
]; ];
/** /**
@ -134,24 +119,19 @@ class ApiKey extends Model
'key_type' => 'present|integer|min:0|max:2', 'key_type' => 'present|integer|min:0|max:2',
'identifier' => 'required|string|size:16|unique:api_keys,identifier', 'identifier' => 'required|string|size:16|unique:api_keys,identifier',
'token' => 'required|string', 'token' => 'required|string',
'permissions' => 'array',
'permissions.*' => 'integer|min:0|max:3',
'memo' => 'required|nullable|string|max:500', 'memo' => 'required|nullable|string|max:500',
'allowed_ips' => 'array', 'allowed_ips' => 'array',
'allowed_ips.*' => 'string', 'allowed_ips.*' => 'string',
'last_used_at' => 'nullable|date', 'last_used_at' => 'nullable|date',
'expires_at' => 'nullable|date', 'expires_at' => 'nullable|date',
'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_EGGS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_MOUNTS => 'integer|min:0|max:3',
]; ];
protected function casts(): array protected function casts(): array
{ {
return [ return [
'permissions' => 'array',
'allowed_ips' => 'array', 'allowed_ips' => 'array',
'user_id' => 'int', 'user_id' => 'int',
'last_used_at' => 'datetime', 'last_used_at' => 'datetime',
@ -159,14 +139,6 @@ class ApiKey extends Model
'token' => 'encrypted', 'token' => 'encrypted',
self::CREATED_AT => 'datetime', self::CREATED_AT => 'datetime',
self::UPDATED_AT => 'datetime', self::UPDATED_AT => 'datetime',
'r_' . AdminAcl::RESOURCE_USERS => 'int',
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int',
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int',
'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'int',
'r_' . AdminAcl::RESOURCE_EGGS => 'int',
'r_' . AdminAcl::RESOURCE_NODES => 'int',
'r_' . AdminAcl::RESOURCE_SERVERS => 'int',
'r_' . AdminAcl::RESOURCE_MOUNTS => 'int',
]; ];
} }
@ -188,6 +160,41 @@ class ApiKey extends Model
return $this->user(); return $this->user();
} }
/**
* Returns the permission for the given resource.
*/
public function getPermission(string $resource): int
{
return $this->permissions[$resource] ?? AdminAcl::NONE;
}
public const DEFAULT_RESOURCE_NAMES = [
Server::RESOURCE_NAME,
Node::RESOURCE_NAME,
Allocation::RESOURCE_NAME,
User::RESOURCE_NAME,
Egg::RESOURCE_NAME,
DatabaseHost::RESOURCE_NAME,
Database::RESOURCE_NAME,
Mount::RESOURCE_NAME,
Role::RESOURCE_NAME,
];
private static array $customResourceNames = [];
public static function registerCustomResourceName(string $resourceName): void
{
$customResourceNames[] = $resourceName;
}
/**
* Returns a list of all possible permission keys.
*/
public static function getPermissionList(): array
{
return array_unique(array_merge(self::DEFAULT_RESOURCE_NAMES, self::$customResourceNames));
}
/** /**
* Finds the model matching the provided token. * Finds the model matching the provided token.
*/ */

View File

@ -23,7 +23,7 @@ class Database extends Model
{ {
/** /**
* The resource name for this model when it is transformed into an * The resource name for this model when it is transformed into an
* API representation using fractal. * API representation using fractal. Also used as name for api key permissions.
*/ */
public const RESOURCE_NAME = 'server_database'; public const RESOURCE_NAME = 'server_database';

View File

@ -21,7 +21,7 @@ class DatabaseHost extends Model
{ {
/** /**
* The resource name for this model when it is transformed into an * The resource name for this model when it is transformed into an
* API representation using fractal. * API representation using fractal. Also used as name for api key permissions.
*/ */
public const RESOURCE_NAME = 'database_host'; public const RESOURCE_NAME = 'database_host';

View File

@ -51,7 +51,7 @@ class Egg extends Model
{ {
/** /**
* The resource name for this model when it is transformed into an * The resource name for this model when it is transformed into an
* API representation using fractal. * API representation using fractal. Also used as name for api key permissions.
*/ */
public const RESOURCE_NAME = 'egg'; public const RESOURCE_NAME = 'egg';

View File

@ -49,7 +49,7 @@ class Node extends Model
/** /**
* The resource name for this model when it is transformed into an * The resource name for this model when it is transformed into an
* API representation using fractal. * API representation using fractal. Also used as name for api key permissions.
*/ */
public const RESOURCE_NAME = 'node'; public const RESOURCE_NAME = 'node';

View File

@ -124,7 +124,7 @@ class Server extends Model
/** /**
* The resource name for this model when it is transformed into an * The resource name for this model when it is transformed into an
* API representation using fractal. * API representation using fractal. Also used as name for api key permissions.
*/ */
public const RESOURCE_NAME = 'server'; public const RESOURCE_NAME = 'server';

View File

@ -104,7 +104,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
/** /**
* The resource name for this model when it is transformed into an * The resource name for this model when it is transformed into an
* API representation using fractal. * API representation using fractal. Also used as name for api key permissions.
*/ */
public const RESOURCE_NAME = 'user'; public const RESOURCE_NAME = 'user';

View File

@ -6,12 +6,6 @@ use App\Models\ApiKey;
class AdminAcl class AdminAcl
{ {
/**
* Resource permission columns in the api_keys table begin
* with this identifier.
*/
public const COLUMN_IDENTIFIER = 'r_';
/** /**
* The different types of permissions available for API keys. This * The different types of permissions available for API keys. This
* implements a read/write/none permissions scheme for all endpoints. * implements a read/write/none permissions scheme for all endpoints.
@ -22,28 +16,6 @@ class AdminAcl
public const WRITE = 2; public const WRITE = 2;
/**
* Resources that are available on the API and can contain a permissions
* set for each key. These are stored in the database as r_{resource}.
*/
public const RESOURCE_SERVERS = 'servers';
public const RESOURCE_NODES = 'nodes';
public const RESOURCE_ALLOCATIONS = 'allocations';
public const RESOURCE_USERS = 'users';
public const RESOURCE_EGGS = 'eggs';
public const RESOURCE_DATABASE_HOSTS = 'database_hosts';
public const RESOURCE_SERVER_DATABASES = 'server_databases';
public const RESOURCE_MOUNTS = 'mounts';
public const RESOURCE_ROLES = 'roles';
/** /**
* Determine if an API key has permission to perform a specific read/write operation. * Determine if an API key has permission to perform a specific read/write operation.
*/ */
@ -62,20 +34,14 @@ class AdminAcl
*/ */
public static function check(ApiKey $key, string $resource, int $action = self::READ): bool public static function check(ApiKey $key, string $resource, int $action = self::READ): bool
{ {
return self::can(data_get($key, self::COLUMN_IDENTIFIER . $resource, self::NONE), $action); return self::can($key->getPermission($resource), $action);
} }
/** /**
* Return a list of all resource constants defined in this ACL. * Returns a list of all possible permissions.
*
* @throws \ReflectionException
*/ */
public static function getResourceList(): array public static function getResourceList(): array
{ {
$reflect = new \ReflectionClass(__CLASS__); return ApiKey::getPermissionList();
return collect($reflect->getConstants())->filter(function ($value, $key) {
return substr($key, 0, 9) === 'RESOURCE_';
})->values()->toArray();
} }
} }

View File

@ -35,7 +35,7 @@ class KeyCreationService
]); ]);
if ($this->keyType === ApiKey::TYPE_APPLICATION) { if ($this->keyType === ApiKey::TYPE_APPLICATION) {
$data = array_merge($data, $permissions); $data['permissions'] = array_merge($data['permissions'], $permissions);
} }
return ApiKey::query()->forceCreate($data); return ApiKey::query()->forceCreate($data);

View File

@ -4,6 +4,7 @@ namespace App\Services\Nodes;
use App\Models\ApiKey; use App\Models\ApiKey;
use App\Models\Node; use App\Models\Node;
use App\Services\Acl\Api\AdminAcl;
use App\Services\Api\KeyCreationService; use App\Services\Api\KeyCreationService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -28,7 +29,7 @@ class NodeAutoDeployService
/** @var ApiKey|null $key */ /** @var ApiKey|null $key */
$key = ApiKey::query() $key = ApiKey::query()
->where('key_type', ApiKey::TYPE_APPLICATION) ->where('key_type', ApiKey::TYPE_APPLICATION)
->where('r_nodes', true) ->whereJsonContains('permissions->' . Node::RESOURCE_NAME, AdminAcl::READ)
->first(); ->first();
// We couldn't find a key that exists for this user with only permission for // We couldn't find a key that exists for this user with only permission for
@ -37,7 +38,7 @@ class NodeAutoDeployService
$key = $this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([ $key = $this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
'memo' => 'Automatically generated node deployment key.', 'memo' => 'Automatically generated node deployment key.',
'user_id' => $request->user()->id, 'user_id' => $request->user()->id,
], ['r_nodes' => true]); ], ['permissions' => [Node::RESOURCE_NAME => AdminAcl::READ]]);
} }
$token = $key->identifier . $key->token; $token = $key->identifier . $key->token;

View File

@ -7,7 +7,6 @@ use App\Models\Server;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use App\Models\Allocation; use App\Models\Allocation;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class AllocationTransformer extends BaseTransformer class AllocationTransformer extends BaseTransformer
{ {
@ -46,7 +45,7 @@ class AllocationTransformer extends BaseTransformer
*/ */
public function includeNode(Allocation $allocation): Item|NullResource public function includeNode(Allocation $allocation): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { if (!$this->authorize(Node::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -64,7 +63,7 @@ class AllocationTransformer extends BaseTransformer
*/ */
public function includeServer(Allocation $allocation): Item|NullResource public function includeServer(Allocation $allocation): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVERS) || !$allocation->server) { if (!$this->authorize(Server::RESOURCE_NAME) || !$allocation->server) {
return $this->null(); return $this->null();
} }

View File

@ -8,7 +8,6 @@ use App\Models\DatabaseHost;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use League\Fractal\Resource\Collection; use League\Fractal\Resource\Collection;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class DatabaseHostTransformer extends BaseTransformer class DatabaseHostTransformer extends BaseTransformer
{ {
@ -49,7 +48,7 @@ class DatabaseHostTransformer extends BaseTransformer
*/ */
public function includeDatabases(DatabaseHost $model): Collection|NullResource public function includeDatabases(DatabaseHost $model): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVER_DATABASES)) { if (!$this->authorize(Database::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -65,7 +64,7 @@ class DatabaseHostTransformer extends BaseTransformer
*/ */
public function includeNode(DatabaseHost $model): Item|NullResource public function includeNode(DatabaseHost $model): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { if (!$this->authorize(Node::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -8,7 +8,6 @@ use App\Models\Server;
use App\Models\EggVariable; use App\Models\EggVariable;
use League\Fractal\Resource\Collection; use League\Fractal\Resource\Collection;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class EggTransformer extends BaseTransformer class EggTransformer extends BaseTransformer
{ {
@ -83,7 +82,7 @@ class EggTransformer extends BaseTransformer
*/ */
public function includeServers(Egg $model): Collection|NullResource public function includeServers(Egg $model): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { if (!$this->authorize(Server::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -99,7 +98,7 @@ class EggTransformer extends BaseTransformer
*/ */
public function includeVariables(Egg $model): Collection|NullResource public function includeVariables(Egg $model): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { if (!$this->authorize(Egg::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -2,10 +2,12 @@
namespace App\Transformers\Api\Application; namespace App\Transformers\Api\Application;
use App\Models\Egg;
use App\Models\Mount; use App\Models\Mount;
use App\Models\Node;
use App\Models\Server;
use League\Fractal\Resource\Collection; use League\Fractal\Resource\Collection;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class MountTransformer extends BaseTransformer class MountTransformer extends BaseTransformer
{ {
@ -34,7 +36,7 @@ class MountTransformer extends BaseTransformer
*/ */
public function includeEggs(Mount $mount): Collection|NullResource public function includeEggs(Mount $mount): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { if (!$this->authorize(Egg::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -54,7 +56,7 @@ class MountTransformer extends BaseTransformer
*/ */
public function includeNodes(Mount $mount): Collection|NullResource public function includeNodes(Mount $mount): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { if (!$this->authorize(Node::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -74,7 +76,7 @@ class MountTransformer extends BaseTransformer
*/ */
public function includeServers(Mount $mount): Collection|NullResource public function includeServers(Mount $mount): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { if (!$this->authorize(Server::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -3,9 +3,10 @@
namespace App\Transformers\Api\Application; namespace App\Transformers\Api\Application;
use App\Models\Node; use App\Models\Node;
use App\Models\Server;
use App\Models\Allocation;
use League\Fractal\Resource\Collection; use League\Fractal\Resource\Collection;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class NodeTransformer extends BaseTransformer class NodeTransformer extends BaseTransformer
{ {
@ -52,7 +53,7 @@ class NodeTransformer extends BaseTransformer
*/ */
public function includeAllocations(Node $node): Collection|NullResource public function includeAllocations(Node $node): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_ALLOCATIONS)) { if (!$this->authorize(Allocation::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -72,7 +73,7 @@ class NodeTransformer extends BaseTransformer
*/ */
public function includeServers(Node $node): Collection|NullResource public function includeServers(Node $node): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { if (!$this->authorize(Server::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -6,7 +6,6 @@ use App\Models\Database;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use App\Models\DatabaseHost; use App\Models\DatabaseHost;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class ServerDatabaseTransformer extends BaseTransformer class ServerDatabaseTransformer extends BaseTransformer
{ {
@ -57,7 +56,7 @@ class ServerDatabaseTransformer extends BaseTransformer
*/ */
public function includeHost(Database $model): Item|NullResource public function includeHost(Database $model): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_DATABASE_HOSTS)) { if (!$this->authorize(DatabaseHost::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -3,10 +3,14 @@
namespace App\Transformers\Api\Application; namespace App\Transformers\Api\Application;
use App\Models\Server; use App\Models\Server;
use App\Models\Node;
use App\Models\User;
use App\Models\Egg;
use App\Models\Database;
use App\Models\Allocation;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use League\Fractal\Resource\Collection; use League\Fractal\Resource\Collection;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
use App\Services\Servers\EnvironmentService; use App\Services\Servers\EnvironmentService;
class ServerTransformer extends BaseTransformer class ServerTransformer extends BaseTransformer
@ -97,7 +101,7 @@ class ServerTransformer extends BaseTransformer
*/ */
public function includeAllocations(Server $server): Collection|NullResource public function includeAllocations(Server $server): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_ALLOCATIONS)) { if (!$this->authorize(Allocation::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -113,7 +117,7 @@ class ServerTransformer extends BaseTransformer
*/ */
public function includeSubusers(Server $server): Collection|NullResource public function includeSubusers(Server $server): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { if (!$this->authorize(User::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -129,7 +133,7 @@ class ServerTransformer extends BaseTransformer
*/ */
public function includeUser(Server $server): Item|NullResource public function includeUser(Server $server): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { if (!$this->authorize(User::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -145,7 +149,7 @@ class ServerTransformer extends BaseTransformer
*/ */
public function includeEgg(Server $server): Item|NullResource public function includeEgg(Server $server): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { if (!$this->authorize(Egg::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -161,7 +165,7 @@ class ServerTransformer extends BaseTransformer
*/ */
public function includeVariables(Server $server): Collection|NullResource public function includeVariables(Server $server): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { if (!$this->authorize(Server::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -177,7 +181,7 @@ class ServerTransformer extends BaseTransformer
*/ */
public function includeNode(Server $server): Item|NullResource public function includeNode(Server $server): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { if (!$this->authorize(Node::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -193,7 +197,7 @@ class ServerTransformer extends BaseTransformer
*/ */
public function includeDatabases(Server $server): Collection|NullResource public function includeDatabases(Server $server): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVER_DATABASES)) { if (!$this->authorize(Database::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -4,8 +4,8 @@ namespace App\Transformers\Api\Application;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use App\Models\EggVariable; use App\Models\EggVariable;
use App\Models\Egg;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class ServerVariableTransformer extends BaseTransformer class ServerVariableTransformer extends BaseTransformer
{ {
@ -37,7 +37,7 @@ class ServerVariableTransformer extends BaseTransformer
*/ */
public function includeParent(EggVariable $variable): Item|NullResource public function includeParent(EggVariable $variable): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { if (!$this->authorize(Egg::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -3,9 +3,10 @@
namespace App\Transformers\Api\Application; namespace App\Transformers\Api\Application;
use App\Models\Subuser; use App\Models\Subuser;
use App\Models\User;
use App\Models\Server;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class SubuserTransformer extends BaseTransformer class SubuserTransformer extends BaseTransformer
{ {
@ -44,7 +45,7 @@ class SubuserTransformer extends BaseTransformer
*/ */
public function includeUser(Subuser $subuser): Item|NullResource public function includeUser(Subuser $subuser): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { if (!$this->authorize(User::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -60,7 +61,7 @@ class SubuserTransformer extends BaseTransformer
*/ */
public function includeServer(Subuser $subuser): Item|NullResource public function includeServer(Subuser $subuser): Item|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { if (!$this->authorize(Server::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -4,9 +4,9 @@ namespace App\Transformers\Api\Application;
use App\Models\Role; use App\Models\Role;
use App\Models\User; use App\Models\User;
use App\Models\Server;
use League\Fractal\Resource\Collection; use League\Fractal\Resource\Collection;
use League\Fractal\Resource\NullResource; use League\Fractal\Resource\NullResource;
use App\Services\Acl\Api\AdminAcl;
class UserTransformer extends BaseTransformer class UserTransformer extends BaseTransformer
{ {
@ -55,7 +55,7 @@ class UserTransformer extends BaseTransformer
*/ */
public function includeServers(User $user): Collection|NullResource public function includeServers(User $user): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { if (!$this->authorize(Server::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }
@ -71,7 +71,7 @@ class UserTransformer extends BaseTransformer
*/ */
public function includeRoles(User $user): Collection|NullResource public function includeRoles(User $user): Collection|NullResource
{ {
if (!$this->authorize(AdminAcl::RESOURCE_ROLES)) { if (!$this->authorize(Role::RESOURCE_NAME)) {
return $this->null(); return $this->null();
} }

View File

@ -28,6 +28,7 @@ class ApiKeyFactory extends Factory
'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION), 'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION),
'token' => $token ?: $token = Str::random(ApiKey::KEY_LENGTH), 'token' => $token ?: $token = Str::random(ApiKey::KEY_LENGTH),
'allowed_ips' => [], 'allowed_ips' => [],
'permissions' => [],
'memo' => 'Test Function Key', 'memo' => 'Test Function Key',
'created_at' => Carbon::now(), 'created_at' => Carbon::now(),
'updated_at' => Carbon::now(), 'updated_at' => Carbon::now(),

View File

@ -24,26 +24,26 @@ return new class extends Migration
DB::transaction(function () { DB::transaction(function () {
// api_keys_user_id_foreign // api_keys_user_id_foreign
DB::statement('ALTER TABLE api_keys RENAME TO _api_keys_old'); DB::statement('ALTER TABLE api_keys RENAME TO _api_keys_old');
DB::statement('CREATE TABLE api_keys DB::statement('CREATE TABLE api_keys
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"token" text not null, "token" text not null,
"allowed_ips" text not null, "allowed_ips" text not null,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
"user_id" integer not null, "user_id" integer not null,
"memo" text, "memo" text,
"r_servers" integer not null default \'0\', "r_servers" integer not null default \'0\',
"r_nodes" integer not null default \'0\', "r_nodes" integer not null default \'0\',
"r_allocations" integer not null default \'0\', "r_allocations" integer not null default \'0\',
"r_users" integer not null default \'0\', "r_users" integer not null default \'0\',
"r_eggs" integer not null default \'0\', "r_eggs" integer not null default \'0\',
"r_database_hosts" integer not null default \'0\', "r_database_hosts" integer not null default \'0\',
"r_server_databases" integer not null default \'0\', "r_server_databases" integer not null default \'0\',
"identifier" varchar, "identifier" varchar,
"key_type" integer not null default \'0\', "key_type" integer not null default \'0\',
"last_used_at" datetime, "last_used_at" datetime,
"expires_at" datetime, "expires_at" datetime,
"r_mounts" integer not null default \'0\', "r_mounts" integer not null default \'0\',
foreign key("user_id") references "users"("id") on delete cascade)'); foreign key("user_id") references "users"("id") on delete cascade)');
DB::statement('INSERT INTO api_keys SELECT * FROM _api_keys_old'); DB::statement('INSERT INTO api_keys SELECT * FROM _api_keys_old');
DB::statement('DROP TABLE _api_keys_old'); DB::statement('DROP TABLE _api_keys_old');
@ -51,17 +51,17 @@ return new class extends Migration
// database_hosts_node_id_foreign // database_hosts_node_id_foreign
DB::statement('ALTER TABLE database_hosts RENAME TO _database_hosts_old'); DB::statement('ALTER TABLE database_hosts RENAME TO _database_hosts_old');
DB::statement('CREATE TABLE database_hosts DB::statement('CREATE TABLE database_hosts
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"name" varchar not null, "name" varchar not null,
"host" varchar not null, "host" varchar not null,
"port" integer not null, "port" integer not null,
"username" varchar not null, "username" varchar not null,
"password" text not null, "password" text not null,
"max_databases" integer, "max_databases" integer,
"node_id" integer, "node_id" integer,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
foreign key("node_id") references "nodes"("id") on delete set null)'); foreign key("node_id") references "nodes"("id") on delete set null)');
DB::statement('INSERT INTO database_hosts SELECT * FROM _database_hosts_old'); DB::statement('INSERT INTO database_hosts SELECT * FROM _database_hosts_old');
DB::statement('DROP TABLE _database_hosts_old'); DB::statement('DROP TABLE _database_hosts_old');
@ -69,10 +69,10 @@ return new class extends Migration
// mount_node_node_id_foreign // mount_node_node_id_foreign
// mount_node_mount_id_foreign // mount_node_mount_id_foreign
DB::statement('ALTER TABLE mount_node RENAME TO _mount_node_old'); DB::statement('ALTER TABLE mount_node RENAME TO _mount_node_old');
DB::statement('CREATE TABLE mount_node DB::statement('CREATE TABLE mount_node
("node_id" integer not null, ("node_id" integer not null,
"mount_id" integer not null, "mount_id" integer not null,
foreign key("node_id") references "nodes"("id") on delete cascade on update cascade, foreign key("node_id") references "nodes"("id") on delete cascade on update cascade,
foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)'); foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)');
DB::statement('INSERT INTO mount_node SELECT * FROM _mount_node_old'); DB::statement('INSERT INTO mount_node SELECT * FROM _mount_node_old');
DB::statement('DROP TABLE _mount_node_old'); DB::statement('DROP TABLE _mount_node_old');
@ -83,38 +83,38 @@ return new class extends Migration
// servers_egg_id_foreign // servers_egg_id_foreign
// servers_allocation_id_foreign // servers_allocation_id_foreign
DB::statement('ALTER TABLE servers RENAME TO _servers_old'); DB::statement('ALTER TABLE servers RENAME TO _servers_old');
DB::statement('CREATE TABLE servers DB::statement('CREATE TABLE servers
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"uuid" varchar not null, "uuid" varchar not null,
"uuid_short" varchar not null, "uuid_short" varchar not null,
"node_id" integer not null, "node_id" integer not null,
"name" varchar not null, "name" varchar not null,
"owner_id" integer not null, "owner_id" integer not null,
"memory" integer not null, "memory" integer not null,
"swap" integer not null, "swap" integer not null,
"disk" integer not null, "disk" integer not null,
"io" integer not null, "io" integer not null,
"cpu" integer not null, "cpu" integer not null,
"egg_id" integer not null, "egg_id" integer not null,
"startup" text not null, "startup" text not null,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
"allocation_id" integer not null, "allocation_id" integer not null,
"image" varchar not null, "image" varchar not null,
"description" text not null, "description" text not null,
"skip_scripts" tinyint(1) not null default \'0\', "skip_scripts" tinyint(1) not null default \'0\',
"external_id" varchar, "external_id" varchar,
"database_limit" integer default \'0\', "database_limit" integer default \'0\',
"allocation_limit" integer, "allocation_limit" integer,
"threads" varchar, "threads" varchar,
"backup_limit" integer not null default \'0\', "backup_limit" integer not null default \'0\',
"status" varchar, "status" varchar,
"installed_at" datetime, "installed_at" datetime,
"oom_killer" integer not null default \'0\', "oom_killer" integer not null default \'0\',
"docker_labels" text, "docker_labels" text,
foreign key("node_id") references "nodes"("id"), foreign key("node_id") references "nodes"("id"),
foreign key("owner_id") references "users"("id"), foreign key("owner_id") references "users"("id"),
foreign key("egg_id") references "eggs"("id"), foreign key("egg_id") references "eggs"("id"),
foreign key("allocation_id") references "allocations"("id"))'); foreign key("allocation_id") references "allocations"("id"))');
DB::statement('INSERT INTO servers SELECT * FROM _servers_old'); DB::statement('INSERT INTO servers SELECT * FROM _servers_old');
DB::statement('DROP TABLE _servers_old'); DB::statement('DROP TABLE _servers_old');
@ -126,18 +126,18 @@ return new class extends Migration
// databases_server_id_foreign // databases_server_id_foreign
// databases_database_host_id_foreign // databases_database_host_id_foreign
DB::statement('ALTER TABLE databases RENAME TO _databases_old'); DB::statement('ALTER TABLE databases RENAME TO _databases_old');
DB::statement('CREATE TABLE databases DB::statement('CREATE TABLE databases
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"server_id" integer not null, "server_id" integer not null,
"database_host_id" integer not null, "database_host_id" integer not null,
"database" varchar not null, "database" varchar not null,
"username" varchar not null, "username" varchar not null,
"remote" varchar not null default \'%\', "remote" varchar not null default \'%\',
"password" text not null, "password" text not null,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
"max_connections" integer default \'0\', "max_connections" integer default \'0\',
foreign key("server_id") references "servers"("id"), foreign key("server_id") references "servers"("id"),
foreign key("database_host_id") references "database_hosts"("id"))'); foreign key("database_host_id") references "database_hosts"("id"))');
DB::statement('INSERT INTO databases SELECT * FROM _databases_old'); DB::statement('INSERT INTO databases SELECT * FROM _databases_old');
DB::statement('DROP TABLE _databases_old'); DB::statement('DROP TABLE _databases_old');
@ -147,16 +147,16 @@ return new class extends Migration
// allocations_node_id_foreign // allocations_node_id_foreign
// allocations_server_id_foreign // allocations_server_id_foreign
DB::statement('ALTER TABLE allocations RENAME TO _allocations_old'); DB::statement('ALTER TABLE allocations RENAME TO _allocations_old');
DB::statement('CREATE TABLE allocations DB::statement('CREATE TABLE allocations
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"node_id" integer not null, "node_id" integer not null,
"ip" varchar not null, "ip" varchar not null,
"port" integer not null, "port" integer not null,
"server_id" integer, "server_id" integer,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
"ip_alias" text, "ip_alias" text,
"notes" varchar, "notes" varchar,
foreign key("node_id") references "nodes"("id") on delete cascade, foreign key("node_id") references "nodes"("id") on delete cascade,
foreign key("server_id") references "servers"("id") on delete cascade on update set null)'); foreign key("server_id") references "servers"("id") on delete cascade on update set null)');
DB::statement('INSERT INTO allocations SELECT * FROM _allocations_old'); DB::statement('INSERT INTO allocations SELECT * FROM _allocations_old');
@ -166,32 +166,32 @@ return new class extends Migration
// eggs_config_from_foreign // eggs_config_from_foreign
// eggs_copy_script_from_foreign // eggs_copy_script_from_foreign
DB::statement('ALTER TABLE eggs RENAME TO _eggs_old'); DB::statement('ALTER TABLE eggs RENAME TO _eggs_old');
DB::statement('CREATE TABLE eggs DB::statement('CREATE TABLE eggs
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"name" varchar not null, "name" varchar not null,
"description" text, "description" text,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
"startup" text, "startup" text,
"config_from" integer, "config_from" integer,
"config_stop" varchar, "config_stop" varchar,
"config_logs" text, "config_logs" text,
"config_startup" text, "config_startup" text,
"config_files" text, "config_files" text,
"script_install" text, "script_install" text,
"script_is_privileged" tinyint(1) not null default \'1\', "script_is_privileged" tinyint(1) not null default \'1\',
"script_entry" varchar not null default \'ash\', "script_entry" varchar not null default \'ash\',
"script_container" varchar not null default \'alpine:3.4\', "script_container" varchar not null default \'alpine:3.4\',
"copy_script_from" integer, "copy_script_from" integer,
"uuid" varchar not null, "uuid" varchar not null,
"author" varchar not null, "author" varchar not null,
"features" text, "features" text,
"docker_images" text, "docker_images" text,
"update_url" text, "update_url" text,
"file_denylist" text, "file_denylist" text,
"force_outgoing_ip" tinyint(1) not null default \'0\', "force_outgoing_ip" tinyint(1) not null default \'0\',
"tags" text not null, "tags" text not null,
foreign key("config_from") references "eggs"("id") on delete set null, foreign key("config_from") references "eggs"("id") on delete set null,
foreign key("copy_script_from") references "eggs"("id") on delete set null)'); foreign key("copy_script_from") references "eggs"("id") on delete set null)');
DB::statement('INSERT INTO eggs SELECT * FROM _eggs_old'); DB::statement('INSERT INTO eggs SELECT * FROM _eggs_old');
DB::statement('DROP TABLE _eggs_old'); DB::statement('DROP TABLE _eggs_old');
@ -200,10 +200,10 @@ return new class extends Migration
// egg_mount_mount_id_foreign // egg_mount_mount_id_foreign
// egg_mount_egg_id_foreign // egg_mount_egg_id_foreign
DB::statement('ALTER TABLE egg_mount RENAME TO _egg_mount_old'); DB::statement('ALTER TABLE egg_mount RENAME TO _egg_mount_old');
DB::statement('CREATE TABLE egg_mount DB::statement('CREATE TABLE egg_mount
("egg_id" integer not null, ("egg_id" integer not null,
"mount_id" integer not null, "mount_id" integer not null,
foreign key("egg_id") references "eggs"("id") on delete cascade on update cascade, foreign key("egg_id") references "eggs"("id") on delete cascade on update cascade,
foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)'); foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)');
DB::statement('INSERT INTO egg_mount SELECT * FROM _egg_mount_old'); DB::statement('INSERT INTO egg_mount SELECT * FROM _egg_mount_old');
DB::statement('DROP TABLE _egg_mount_old'); DB::statement('DROP TABLE _egg_mount_old');
@ -211,19 +211,19 @@ return new class extends Migration
// service_variables_egg_id_foreign // service_variables_egg_id_foreign
DB::statement('ALTER TABLE egg_variables RENAME TO _egg_variables_old'); DB::statement('ALTER TABLE egg_variables RENAME TO _egg_variables_old');
DB::statement('CREATE TABLE egg_variables DB::statement('CREATE TABLE egg_variables
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"egg_id" integer not null, "egg_id" integer not null,
"name" varchar not null, "name" varchar not null,
"description" text not null, "description" text not null,
"env_variable" varchar not null, "env_variable" varchar not null,
"default_value" text not null, "default_value" text not null,
"user_viewable" integer not null, "user_viewable" integer not null,
"user_editable" integer not null, "user_editable" integer not null,
"rules" text not null, "rules" text not null,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
"sort" integer, "sort" integer,
foreign key("egg_id") references "eggs"("id") on delete cascade)'); foreign key("egg_id") references "eggs"("id") on delete cascade)');
DB::statement('INSERT INTO egg_variables SELECT * FROM _egg_variables_old'); DB::statement('INSERT INTO egg_variables SELECT * FROM _egg_variables_old');
DB::statement('DROP TABLE _egg_variables_old'); DB::statement('DROP TABLE _egg_variables_old');
@ -231,10 +231,10 @@ return new class extends Migration
// mount_server_server_id_foreign // mount_server_server_id_foreign
// mount_server_mount_id_foreign // mount_server_mount_id_foreign
DB::statement('ALTER TABLE mount_server RENAME TO _mount_server_old'); DB::statement('ALTER TABLE mount_server RENAME TO _mount_server_old');
DB::statement('CREATE TABLE mount_server DB::statement('CREATE TABLE mount_server
("server_id" integer not null, ("server_id" integer not null,
"mount_id" integer not null, "mount_id" integer not null,
foreign key("server_id") references "servers"("id") on delete cascade on update cascade, foreign key("server_id") references "servers"("id") on delete cascade on update cascade,
foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)'); foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)');
DB::statement('INSERT INTO mount_server SELECT * FROM _mount_server_old'); DB::statement('INSERT INTO mount_server SELECT * FROM _mount_server_old');
DB::statement('DROP TABLE _mount_server_old'); DB::statement('DROP TABLE _mount_server_old');
@ -242,14 +242,14 @@ return new class extends Migration
// server_variables_variable_id_foreign // server_variables_variable_id_foreign
DB::statement('ALTER TABLE server_variables RENAME TO _server_variables_old'); DB::statement('ALTER TABLE server_variables RENAME TO _server_variables_old');
DB::statement('CREATE TABLE server_variables DB::statement('CREATE TABLE server_variables
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"server_id" integer not null, "server_id" integer not null,
"variable_id" integer not null, "variable_id" integer not null,
"variable_value" text not null, "variable_value" text not null,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
foreign key("server_id") references "servers"("id") on delete cascade, foreign key("server_id") references "servers"("id") on delete cascade,
foreign key("variable_id") references "egg_variables"("id") on delete cascade)'); foreign key("variable_id") references "egg_variables"("id") on delete cascade)');
DB::statement('INSERT INTO server_variables SELECT * FROM _server_variables_old'); DB::statement('INSERT INTO server_variables SELECT * FROM _server_variables_old');
DB::statement('DROP TABLE _server_variables_old'); DB::statement('DROP TABLE _server_variables_old');
@ -257,14 +257,14 @@ return new class extends Migration
// subusers_user_id_foreign // subusers_user_id_foreign
// subusers_server_id_foreign // subusers_server_id_foreign
DB::statement('ALTER TABLE subusers RENAME TO _subusers_old'); DB::statement('ALTER TABLE subusers RENAME TO _subusers_old');
DB::statement('CREATE TABLE subusers DB::statement('CREATE TABLE subusers
("id" integer primary key autoincrement not null, ("id" integer primary key autoincrement not null,
"user_id" integer not null, "user_id" integer not null,
"server_id" integer not null, "server_id" integer not null,
"created_at" datetime, "created_at" datetime,
"updated_at" datetime, "updated_at" datetime,
"permissions" text, "permissions" text,
foreign key("user_id") references "users"("id") on delete cascade, foreign key("user_id") references "users"("id") on delete cascade,
foreign key("server_id") references "servers"("id") on delete cascade)'); foreign key("server_id") references "servers"("id") on delete cascade)');
DB::statement('INSERT INTO subusers SELECT * FROM _subusers_old'); DB::statement('INSERT INTO subusers SELECT * FROM _subusers_old');
DB::statement('DROP TABLE _subusers_old'); DB::statement('DROP TABLE _subusers_old');

View File

@ -0,0 +1,94 @@
<?php
use App\Models\Allocation;
use App\Models\ApiKey;
use App\Models\Database;
use App\Models\DatabaseHost;
use App\Models\Egg;
use App\Models\Mount;
use App\Models\Node;
use App\Models\Server;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('api_keys', function (Blueprint $table) {
$table->json('permissions');
});
foreach (ApiKey::query() as $apiKey) {
$permissions = [
Server::RESOURCE_NAME => intval($apiKey->r_servers ?? 0),
Node::RESOURCE_NAME => intval($apiKey->r_nodes ?? 0),
Allocation::RESOURCE_NAME => intval($apiKey->r_allocations ?? 0),
User::RESOURCE_NAME => intval($apiKey->r_users ?? 0),
Egg::RESOURCE_NAME => intval($apiKey->r_eggs ?? 0),
DatabaseHost::RESOURCE_NAME => intval($apiKey->r_database_hosts ?? 0),
Database::RESOURCE_NAME => intval($apiKey->r_server_databases ?? 0),
Mount::RESOURCE_NAME => intval($apiKey->r_mounts ?? 0),
];
DB::table('api_keys')
->where('id', $apiKey->id)
->update(['permissions' => $permissions]);
}
Schema::table('api_keys', function (Blueprint $table) {
$table->dropColumn([
'r_servers',
'r_nodes',
'r_allocations',
'r_users',
'r_eggs',
'r_database_hosts',
'r_server_databases',
'r_mounts',
]);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('api_keys', function (Blueprint $table) {
$table->unsignedTinyInteger('r_servers')->default(0);
$table->unsignedTinyInteger('r_nodes')->default(0);
$table->unsignedTinyInteger('r_allocations')->default(0);
$table->unsignedTinyInteger('r_users')->default(0);
$table->unsignedTinyInteger('r_eggs')->default(0);
$table->unsignedTinyInteger('r_database_hosts')->default(0);
$table->unsignedTinyInteger('r_server_databases')->default(0);
$table->unsignedTinyInteger('r_mounts')->default(0);
});
foreach (ApiKey::query() as $apiKey) {
DB::table('api_keys')
->where('id', $apiKey->id)
->update([
'r_servers' => $apiKey->permissions[Server::RESOURCE_NAME],
'r_nodes' => $apiKey->permissions[Node::RESOURCE_NAME],
'r_allocations' => $apiKey->permissions[Allocation::RESOURCE_NAME],
'r_users' => $apiKey->permissions[User::RESOURCE_NAME],
'r_eggs' => $apiKey->permissions[Egg::RESOURCE_NAME],
'r_database_hosts' => $apiKey->permissions[DatabaseHost::RESOURCE_NAME],
'r_server_databases' => $apiKey->permissions[Database::RESOURCE_NAME],
'r_mounts' => $apiKey->permissions[Mount::RESOURCE_NAME],
]);
}
Schema::table('api_keys', function (Blueprint $table) {
$table->dropColumn('permissions');
});
}
};

View File

@ -2,10 +2,17 @@
namespace App\Tests\Integration\Api\Application; namespace App\Tests\Integration\Api\Application;
use App\Models\Allocation;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\User; use App\Models\User;
use PHPUnit\Framework\Assert; use PHPUnit\Framework\Assert;
use App\Models\ApiKey; use App\Models\ApiKey;
use App\Models\Database;
use App\Models\DatabaseHost;
use App\Models\Egg;
use App\Models\Mount;
use App\Models\Node;
use App\Models\Server;
use App\Models\Role; use App\Models\Role;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Tests\Integration\IntegrationTestCase; use App\Tests\Integration\IntegrationTestCase;
@ -79,18 +86,21 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase
*/ */
protected function createApiKey(User $user, array $permissions = []): ApiKey protected function createApiKey(User $user, array $permissions = []): ApiKey
{ {
return ApiKey::factory()->create(array_merge([ return ApiKey::factory()->create([
'user_id' => $user->id, 'user_id' => $user->id,
'key_type' => ApiKey::TYPE_APPLICATION, 'key_type' => ApiKey::TYPE_APPLICATION,
'r_servers' => AdminAcl::READ | AdminAcl::WRITE, 'permissions' => array_merge([
'r_nodes' => AdminAcl::READ | AdminAcl::WRITE, Server::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
'r_allocations' => AdminAcl::READ | AdminAcl::WRITE, Node::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
'r_users' => AdminAcl::READ | AdminAcl::WRITE, Allocation::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
'r_eggs' => AdminAcl::READ | AdminAcl::WRITE, User::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
'r_database_hosts' => AdminAcl::READ | AdminAcl::WRITE, Egg::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
'r_server_databases' => AdminAcl::READ | AdminAcl::WRITE, DatabaseHost::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
'r_mounts' => AdminAcl::READ | AdminAcl::WRITE, Database::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
], $permissions)); Mount::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
Role::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
], $permissions),
]);
} }
/** /**

View File

@ -3,6 +3,7 @@
namespace App\Tests\Integration\Api\Application; namespace App\Tests\Integration\Api\Application;
use App\Models\Egg; use App\Models\Egg;
use App\Services\Acl\Api\AdminAcl;
use App\Transformers\Api\Application\EggTransformer; use App\Transformers\Api\Application\EggTransformer;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
@ -111,7 +112,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase
public function testErrorReturnedIfNoPermission(): void public function testErrorReturnedIfNoPermission(): void
{ {
$egg = Egg::query()->findOrFail(1); $egg = Egg::query()->findOrFail(1);
$this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); $this->createNewDefaultApiKey($this->getApiUser(), [Egg::RESOURCE_NAME => AdminAcl::NONE]);
$response = $this->getJson('/api/application/eggs'); $response = $this->getJson('/api/application/eggs');
$this->assertAccessDeniedJson($response); $this->assertAccessDeniedJson($response);

View File

@ -4,6 +4,7 @@ namespace App\Tests\Integration\Api\Application\Users;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Models\User; use App\Models\User;
use App\Services\Acl\Api\AdminAcl;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use App\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; use App\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase;
@ -62,7 +63,7 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase
public function testErrorReturnedIfNoPermission(): void public function testErrorReturnedIfNoPermission(): void
{ {
$user = User::factory()->create(['external_id' => Str::random()]); $user = User::factory()->create(['external_id' => Str::random()]);
$this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); $this->createNewDefaultApiKey($this->getApiUser(), [User::RESOURCE_NAME => AdminAcl::NONE]);
$response = $this->getJson('/api/application/users/external/' . $user->external_id); $response = $this->getJson('/api/application/users/external/' . $user->external_id);
$this->assertAccessDeniedJson($response); $this->assertAccessDeniedJson($response);

View File

@ -2,6 +2,7 @@
namespace App\Tests\Integration\Api\Application\Users; namespace App\Tests\Integration\Api\Application\Users;
use App\Models\Server;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
@ -152,7 +153,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase
*/ */
public function testKeyWithoutPermissionCannotLoadRelationship(): void public function testKeyWithoutPermissionCannotLoadRelationship(): void
{ {
$this->createNewDefaultApiKey($this->getApiUser(), ['r_servers' => 0]); $this->createNewDefaultApiKey($this->getApiUser(), [Server::RESOURCE_NAME => AdminAcl::NONE]);
$user = User::factory()->create(); $user = User::factory()->create();
$this->createServerModel(['user_id' => $user->id]); $this->createServerModel(['user_id' => $user->id]);
@ -197,7 +198,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase
public function testErrorReturnedIfNoPermission(): void public function testErrorReturnedIfNoPermission(): void
{ {
$user = User::factory()->create(); $user = User::factory()->create();
$this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); $this->createNewDefaultApiKey($this->getApiUser(), [User::RESOURCE_NAME => AdminAcl::NONE]);
$response = $this->getJson('/api/application/users/' . $user->id); $response = $this->getJson('/api/application/users/' . $user->id);
$this->assertAccessDeniedJson($response); $this->assertAccessDeniedJson($response);
@ -286,7 +287,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase
*/ */
public function testApiKeyWithoutWritePermissions(string $method, string $url): void public function testApiKeyWithoutWritePermissions(string $method, string $url): void
{ {
$this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => AdminAcl::READ]); $this->createNewDefaultApiKey($this->getApiUser(), [User::RESOURCE_NAME => AdminAcl::READ]);
if (str_contains($url, '{id}')) { if (str_contains($url, '{id}')) {
$user = User::factory()->create(); $user = User::factory()->create();

View File

@ -5,6 +5,7 @@ namespace App\Tests\Unit\Services\Acl\Api;
use App\Models\ApiKey; use App\Models\ApiKey;
use App\Tests\TestCase; use App\Tests\TestCase;
use App\Services\Acl\Api\AdminAcl; use App\Services\Acl\Api\AdminAcl;
use App\Models\Server;
class AdminAclTest extends TestCase class AdminAclTest extends TestCase
{ {
@ -23,9 +24,11 @@ class AdminAclTest extends TestCase
*/ */
public function testCheck(): void public function testCheck(): void
{ {
$model = ApiKey::factory()->make(['r_servers' => AdminAcl::READ | AdminAcl::WRITE]); $model = ApiKey::factory()->make(['permissions' => [
Server::RESOURCE_NAME => AdminAcl::READ | AdminAcl::WRITE,
]]);
$this->assertTrue(AdminAcl::check($model, AdminAcl::RESOURCE_SERVERS, AdminAcl::WRITE)); $this->assertTrue(AdminAcl::check($model, Server::RESOURCE_NAME, AdminAcl::WRITE));
} }
/** /**