diff --git a/app/Console/Commands/Server/RebuildServerCommand.php b/app/Console/Commands/Server/RebuildServerCommand.php index c61ae98d9..ee94e85dc 100644 --- a/app/Console/Commands/Server/RebuildServerCommand.php +++ b/app/Console/Commands/Server/RebuildServerCommand.php @@ -104,10 +104,7 @@ class RebuildServerCommand extends Command ]; try { - $this->daemonRepository->setNode($server->node_id) - ->setAccessServer($server->uuid) - ->setAccessToken($server->node->daemonSecret) - ->update($json); + $this->daemonRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update($json); } catch (RequestException $exception) { $this->output->error(trans('command/messages.server.rebuild_failed', [ 'name' => $server->name, diff --git a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php index 703736547..0b5aeed30 100644 --- a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php @@ -36,15 +36,6 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface */ public function create($id, array $overrides = [], $start = false); - /** - * Set an access token and associated permissions for a server. - * - * @param string $key - * @param array $permissions - * @return \Psr\Http\Message\ResponseInterface - */ - public function setSubuserKey($key, array $permissions); - /** * Update server details on the daemon. * @@ -95,4 +86,12 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface * @return \Psr\Http\Message\ResponseInterface */ public function details(); + + /** + * Revoke an access key on the daemon before the time is expired. + * + * @param string $key + * @return \Psr\Http\Message\ResponseInterface + */ + public function revokeAccessKey($key); } diff --git a/app/Contracts/Repository/DaemonKeyRepositoryInterface.php b/app/Contracts/Repository/DaemonKeyRepositoryInterface.php index fff8f72f5..154dcb353 100644 --- a/app/Contracts/Repository/DaemonKeyRepositoryInterface.php +++ b/app/Contracts/Repository/DaemonKeyRepositoryInterface.php @@ -26,6 +26,11 @@ namespace Pterodactyl\Contracts\Repository; interface DaemonKeyRepositoryInterface extends RepositoryInterface { + /** + * String prepended to keys to identify that they are managed internally and not part of the user API. + */ + const INTERNAL_KEY_IDENTIFIER = 'i_'; + /** * Gets the daemon keys associated with a specific server. * diff --git a/app/Contracts/Repository/SubuserRepositoryInterface.php b/app/Contracts/Repository/SubuserRepositoryInterface.php index 9ea9f7b0b..aac182e95 100644 --- a/app/Contracts/Repository/SubuserRepositoryInterface.php +++ b/app/Contracts/Repository/SubuserRepositoryInterface.php @@ -30,7 +30,7 @@ interface SubuserRepositoryInterface extends RepositoryInterface * Return a subuser with the associated server relationship. * * @param int $id - * @return \Illuminate\Database\Eloquent\Collection + * @return \Pterodactyl\Models\Subuser * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ diff --git a/app/Exceptions/Service/Server/UserNotLinkedToServerException.php b/app/Exceptions/Service/Server/UserNotLinkedToServerException.php deleted file mode 100644 index 346b41fe7..000000000 --- a/app/Exceptions/Service/Server/UserNotLinkedToServerException.php +++ /dev/null @@ -1,31 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Exceptions\Service\Server; - -use Pterodactyl\Exceptions\PterodactylException; - -class UserNotLinkedToServerException extends PterodactylException -{ -} diff --git a/app/Http/Controllers/API/Remote/ValidateKeyController.php b/app/Http/Controllers/API/Remote/ValidateKeyController.php index ef49b8756..0456d114c 100644 --- a/app/Http/Controllers/API/Remote/ValidateKeyController.php +++ b/app/Http/Controllers/API/Remote/ValidateKeyController.php @@ -30,7 +30,6 @@ use Illuminate\Contracts\Foundation\Application; use Illuminate\Foundation\Testing\HttpException; use League\Fractal\Serializer\JsonApiSerializer; use Pterodactyl\Transformers\Daemon\ApiKeyTransformer; -use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; class ValidateKeyController extends Controller @@ -78,7 +77,7 @@ class ValidateKeyController extends Controller */ public function index($token) { - if (! starts_with($token, DaemonKeyUpdateService::INTERNAL_TOKEN_IDENTIFIER)) { + if (! starts_with($token, DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER)) { throw new HttpException(501); } diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index 517b4caea..03e7fbb5b 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -29,22 +29,22 @@ use Illuminate\Http\Request; use GuzzleHttp\Exception\RequestException; use Pterodactyl\Http\Controllers\Controller; use Symfony\Component\HttpKernel\Exception\HttpException; -use Pterodactyl\Services\Servers\ServerAccessHelperService; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class IndexController extends Controller { - /** - * @var \Pterodactyl\Services\Servers\ServerAccessHelperService - */ - protected $serverAccessHelper; - /** * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface */ protected $daemonRepository; + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService + */ + protected $keyProviderService; + /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ @@ -53,17 +53,17 @@ class IndexController extends Controller /** * IndexController constructor. * + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Services\Servers\ServerAccessHelperService $serverAccessHelper * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository */ public function __construct( + DaemonKeyProviderService $keyProviderService, DaemonServerRepositoryInterface $daemonRepository, - ServerAccessHelperService $serverAccessHelper, ServerRepositoryInterface $repository ) { - $this->serverAccessHelper = $serverAccessHelper; $this->daemonRepository = $daemonRepository; + $this->keyProviderService = $keyProviderService; $this->repository = $repository; } @@ -93,7 +93,7 @@ class IndexController extends Controller public function status(Request $request, $uuid) { $server = $this->repository->findFirstWhere([['uuidShort', '=', $uuid]]); - $token = $this->serverAccessHelper->handle($server, $request->user()); + $token = $this->keyProviderService->handle($server->id, $request->user()->id); if (! $server->installed) { return response()->json(['status' => 20]); diff --git a/app/Http/Middleware/SubuserAccessAuthenticate.php b/app/Http/Middleware/SubuserAccessAuthenticate.php index b3b54fb0b..6b629f5f2 100644 --- a/app/Http/Middleware/SubuserAccessAuthenticate.php +++ b/app/Http/Middleware/SubuserAccessAuthenticate.php @@ -28,15 +28,15 @@ use Closure; use Illuminate\Http\Request; use Illuminate\Contracts\Session\Session; use Illuminate\Auth\AuthenticationException; -use Pterodactyl\Services\Servers\ServerAccessHelperService; -use Pterodactyl\Exceptions\Service\Server\UserNotLinkedToServerException; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; class SubuserAccessAuthenticate { /** - * @var \Pterodactyl\Services\Servers\ServerAccessHelperService + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService */ - protected $accessHelperService; + protected $keyProviderService; /** * @var \Illuminate\Contracts\Session\Session @@ -46,23 +46,26 @@ class SubuserAccessAuthenticate /** * SubuserAccessAuthenticate constructor. * - * @param \Pterodactyl\Services\Servers\ServerAccessHelperService $accessHelperService - * @param \Illuminate\Contracts\Session\Session $session + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService + * @param \Illuminate\Contracts\Session\Session $session */ public function __construct( - ServerAccessHelperService $accessHelperService, + DaemonKeyProviderService $keyProviderService, Session $session ) { - $this->accessHelperService = $accessHelperService; + $this->keyProviderService = $keyProviderService; $this->session = $session; } /** + * Determine if a subuser has permissions to access a server, if so set thier access token. + * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed * * @throws \Illuminate\Auth\AuthenticationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function handle(Request $request, Closure $next) @@ -70,9 +73,9 @@ class SubuserAccessAuthenticate $server = $this->session->get('server_data.model'); try { - $token = $this->accessHelperService->handle($server, $request->user()); + $token = $this->keyProviderService->handle($server->id, $request->user()->id); $this->session->now('server_data.token', $token); - } catch (UserNotLinkedToServerException $exception) { + } catch (RecordNotFoundException $exception) { throw new AuthenticationException('This account does not have permission to access this server.'); } diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index f933af0dc..a81a4ee73 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -110,13 +110,13 @@ class RunTaskJob extends Job implements ShouldQueue case 'power': $this->powerRepository->setNode($server->node_id) ->setAccessServer($server->uuid) - ->setAccessToken($server->daemonSecret) + ->setAccessToken($server->accessToken->secret) ->sendSignal($task->payload); break; case 'command': $this->commandRepository->setNode($server->node_id) ->setAccessServer($server->uuid) - ->setAccessToken($server->daemonSecret) + ->setAccessToken($server->accessToken->secret) ->send($task->payload); break; default: diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 4f11dcc2f..3ad1b946e 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -25,12 +25,14 @@ namespace Pterodactyl\Models; use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Permission extends Model implements CleansAttributes +class Permission extends Model implements CleansAttributes, ValidableContract { - use Eloquence; + use Eloquence, Validable; /** * Should timestamps be used on this model. @@ -62,6 +64,22 @@ class Permission extends Model implements CleansAttributes 'subuser_id' => 'integer', ]; + /** + * @var array + */ + protected static $applicationRules = [ + 'subuser_id' => 'required', + 'permission' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'subuser_id' => 'numeric|min:1', + 'permission' => 'string', + ]; + /** * A list of all permissions available for a user. * diff --git a/app/Models/Server.php b/app/Models/Server.php index 66c5cfe3c..ae4d6117b 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -58,6 +58,13 @@ class Server extends Model implements CleansAttributes, ValidableContract */ protected $dates = ['deleted_at']; + /** + * Always eager load these relationships on the model. + * + * @var array + */ + protected $with = ['key']; + /** * Fields that are not mass assignable. * @@ -286,7 +293,7 @@ class Server extends Model implements CleansAttributes, ValidableContract * * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function ownerKey() + public function key() { return $this->hasOne(DaemonKey::class, 'user_id', 'owner_id'); } diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index bdd532465..76d9955b5 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -104,4 +104,14 @@ class Subuser extends Model implements CleansAttributes, ValidableContract { return $this->hasMany(Permission::class); } + + /** + * Return the key that belongs to this subuser for the server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function key() + { + return $this->hasOne(DaemonKey::class, 'server_id', 'server_id')->where('daemon_keys.user_id', '=', $this->user_id); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 40ca19d2f..2a1151f65 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -24,14 +24,11 @@ namespace Pterodactyl\Models; -use Hash; -use Google2FA; use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Validable; use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Pterodactyl\Exceptions\DisplayException; use Sofa\Eloquence\Contracts\CleansAttributes; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Foundation\Auth\Access\Authorizable; @@ -69,7 +66,18 @@ class User extends Model implements * * @var array */ - protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar', 'root_admin']; + protected $fillable = [ + 'username', + 'email', + 'name_first', + 'name_last', + 'password', + 'language', + 'use_totp', + 'totp_secret', + 'gravatar', + 'root_admin', + ]; /** * Cast values to correct type. @@ -144,46 +152,6 @@ class User extends Model implements 'totp_secret' => 'nullable|string', ]; - /** - * Enables or disables TOTP on an account if the token is valid. - * - * @param int $token - * @return bool - * @deprecated - */ - public function toggleTotp($token) - { - if (! Google2FA::verifyKey($this->totp_secret, $token, 1)) { - return false; - } - - $this->use_totp = ! $this->use_totp; - - return $this->save(); - } - - /** - * Set a user password to a new value assuming it meets the following requirements: - * - 8 or more characters in length - * - at least one uppercase character - * - at least one lowercase character - * - at least one number. - * - * @param string $password - * @param string $regex - * @throws \Pterodactyl\Exceptions\DisplayException - * @deprecated - */ - public function setPassword($password, $regex = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})') - { - if (! preg_match($regex, $password)) { - throw new DisplayException('The password passed did not meet the minimum password requirements.'); - } - - $this->password = Hash::make($password); - $this->save(); - } - /** * Send the password reset notification. * @@ -194,102 +162,6 @@ class User extends Model implements $this->notify(new ResetPasswordNotification($token)); } - /** - * Return true or false depending on wether the user is root admin or not. - * - * @return bool - * @deprecated - */ - public function isRootAdmin() - { - return $this->root_admin; - } - - /** - * Returns the user's daemon secret for a given server. - * - * @param \Pterodactyl\Models\Server $server - * @return null|string - */ - public function daemonToken(Server $server) - { - if ($this->id === $server->owner_id || $this->isRootAdmin()) { - return $server->daemonSecret; - } - - $subuser = $this->subuserOf->where('server_id', $server->id)->first(); - - return ($subuser) ? $subuser->daemonSecret : null; - } - - /** - * Returns an array of all servers a user is able to access. - * Note: does not account for user admin status. - * - * @return array - */ - public function serverAccessArray() - { - return Server::select('id')->where('owner_id', $this->id)->union( - Subuser::select('server_id')->where('user_id', $this->id) - )->pluck('id')->all(); - } - - /** - * Change the access level for a given call to `access()` on the user. - * - * @param string $level can be all, admin, subuser, owner - * @return $this - */ - public function setAccessLevel($level = 'all') - { - if (! in_array($level, ['all', 'admin', 'subuser', 'owner'])) { - $level = 'all'; - } - $this->accessLevel = $level; - - return $this; - } - - /** - * Returns an array of all servers a user is able to access. - * Note: does not account for user admin status. - * - * @param array $load - * @return \Pterodactyl\Models\Server - */ - public function access(...$load) - { - if (count($load) > 0 && is_null($load[0])) { - $query = Server::query(); - } else { - $query = Server::with(! empty($load) ? $load : ['service', 'node', 'allocation']); - } - - // If access level is set to owner, only display servers - // that the user owns. - if ($this->accessLevel === 'owner') { - $query->where('owner_id', $this->id); - } - - // If set to all, display all servers they can access, including - // those they access as an admin. - // - // If set to subuser, only return the servers they can access because - // they are owner, or marked as a subuser of the server. - if (($this->accessLevel === 'all' && ! $this->isRootAdmin()) || $this->accessLevel === 'subuser') { - $query->whereIn('id', $this->serverAccessArray()); - } - - // If set to admin, only display the servers a user can access - // as an administrator (leaves out owned and subuser of). - if ($this->accessLevel === 'admin' && $this->isRootAdmin()) { - $query->whereNotIn('id', $this->serverAccessArray()); - } - - return $query; - } - /** * Store the username as a lowecase string. * @@ -339,4 +211,14 @@ class User extends Model implements { return $this->hasMany(Subuser::class); } + + /** + * Return all of the daemon keys that a user belongs to. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function keys() + { + return $this->hasMany(DaemonKey::class); + } } diff --git a/app/Repositories/Daemon/ServerRepository.php b/app/Repositories/Daemon/ServerRepository.php index db2f31e6e..9f433c3ea 100644 --- a/app/Repositories/Daemon/ServerRepository.php +++ b/app/Repositories/Daemon/ServerRepository.php @@ -31,8 +31,6 @@ use Pterodactyl\Contracts\Repository\ServerRepositoryInterface as DatabaseServer class ServerRepository extends BaseRepository implements ServerRepositoryInterface { - const DAEMON_PERMISSIONS = ['s:*']; - /** * {@inheritdoc} */ @@ -73,9 +71,6 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa ], 'rebuild' => false, 'start_on_completion' => $start, - 'keys' => [ - (string) $server->daemonSecret => self::DAEMON_PERMISSIONS, - ], ]; // Loop through overrides. @@ -88,22 +83,6 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa ]); } - /** - * {@inheritdoc} - */ - public function setSubuserKey($key, array $permissions) - { - Assert::stringNotEmpty($key, 'First argument passed to setSubuserKey must be a non-empty string, received %s.'); - - return $this->getHttpClient()->request('PATCH', '/server', [ - 'json' => [ - 'keys' => [ - $key => $permissions, - ], - ], - ]); - } - /** * {@inheritdoc} */ @@ -169,4 +148,14 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa { return $this->getHttpClient()->request('GET', '/servers'); } + + /** + * {@inheritdoc} + */ + public function revokeAccessKey($key) + { + Assert::stringNotEmpty($key, 'First argument passed to revokeAccessKey must be a non-empty string, received %s.'); + + return $this->getHttpClient()->request('DELETE', '/keys/' . $key); + } } diff --git a/app/Services/DaemonKeys/DaemonKeyCreationService.php b/app/Services/DaemonKeys/DaemonKeyCreationService.php new file mode 100644 index 000000000..f2b8db990 --- /dev/null +++ b/app/Services/DaemonKeys/DaemonKeyCreationService.php @@ -0,0 +1,91 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\DaemonKeys; + +use Carbon\Carbon; +use Webmozart\Assert\Assert; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class DaemonKeyCreationService +{ + /** + * @var \Carbon\Carbon + */ + protected $carbon; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + protected $repository; + + /** + * DaemonKeyCreationService constructor. + * + * @param \Carbon\Carbon $carbon + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository + */ + public function __construct( + Carbon $carbon, + ConfigRepository $config, + DaemonKeyRepositoryInterface $repository + ) { + $this->carbon = $carbon; + $this->config = $config; + $this->repository = $repository; + } + + /** + * Create a new daemon key to be used when connecting to a daemon. + * + * @param int $server + * @param int $user + * @return string + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle($server, $user) + { + Assert::integerish($server, 'First argument passed to handle must be an integer, received %s.'); + Assert::integerish($user, 'Second argument passed to handle must be an integer, received %s.'); + + $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40); + + $this->repository->withoutFresh()->create([ + 'user_id' => $user, + 'server_id' => $server, + 'secret' => $secret, + 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time'))->toDateTimeString(), + ]); + + return $secret; + } +} diff --git a/app/Services/DaemonKeys/DaemonKeyDeletionService.php b/app/Services/DaemonKeys/DaemonKeyDeletionService.php new file mode 100644 index 000000000..6cc605eb8 --- /dev/null +++ b/app/Services/DaemonKeys/DaemonKeyDeletionService.php @@ -0,0 +1,124 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\DaemonKeys; + +use Illuminate\Log\Writer; +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class DaemonKeyDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * DaemonKeyDeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $connection, + DaemonKeyRepositoryInterface $repository, + DaemonServerRepositoryInterface $daemonRepository, + ServerRepositoryInterface $serverRepository, + Writer $writer + ) { + $this->connection = $connection; + $this->daemonRepository = $daemonRepository; + $this->repository = $repository; + $this->serverRepository = $serverRepository; + $this->writer = $writer; + } + + /** + * @param \Pterodactyl\Models\Server|int $server + * @param int $user + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($server, $user) + { + Assert::integerish($user, 'Second argument passed to handle must be an integer, received %s.'); + + if (! $server instanceof Server) { + $server = $this->serverRepository->find($server); + } + + $this->connection->beginTransaction(); + $key = $this->repository->findFirstWhere([ + ['user_id', '=', $user], + ['server_id', '=', $server->id], + ]); + + $this->repository->delete($key->id); + + try { + $this->daemonRepository->setNode($server->node_id)->revokeAccessKey($key->secret); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->connection->rollBack(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + + $this->connection->commit(); + } +} diff --git a/app/Services/DaemonKeys/DaemonKeyProviderService.php b/app/Services/DaemonKeys/DaemonKeyProviderService.php new file mode 100644 index 000000000..db77f70fc --- /dev/null +++ b/app/Services/DaemonKeys/DaemonKeyProviderService.php @@ -0,0 +1,92 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\DaemonKeys; + +use Carbon\Carbon; +use Webmozart\Assert\Assert; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class DaemonKeyProviderService +{ + /** + * @var \Carbon\Carbon + */ + protected $carbon; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService + */ + protected $keyUpdateService; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + protected $repository; + + /** + * GetDaemonKeyService constructor. + * + * @param \Carbon\Carbon $carbon + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $keyUpdateService + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository + */ + public function __construct( + Carbon $carbon, + DaemonKeyUpdateService $keyUpdateService, + DaemonKeyRepositoryInterface $repository + ) { + $this->carbon = $carbon; + $this->keyUpdateService = $keyUpdateService; + $this->repository = $repository; + } + + /** + * Get the access key for a user on a specific server. + * + * @param int $server + * @param int $user + * @param bool $updateIfExpired + * @return string + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($server, $user, $updateIfExpired = true) + { + Assert::integerish($server, 'First argument passed to handle must be an integer, received %s.'); + Assert::integerish($user, 'Second argument passed to handle must be an integer, received %s.'); + + $key = $this->repository->findFirstWhere([ + ['user_id', '=', $user], + ['server_id', '=', $server], + ]); + + if (! $updateIfExpired || max($this->carbon->now()->diffInSeconds($key->expires_at, false), 0) > 0) { + return $key->secret; + } + + return $this->keyUpdateService->handle($key->id); + } +} diff --git a/app/Services/DaemonKeys/DaemonKeyUpdateService.php b/app/Services/DaemonKeys/DaemonKeyUpdateService.php index 794940e82..337b3d173 100644 --- a/app/Services/DaemonKeys/DaemonKeyUpdateService.php +++ b/app/Services/DaemonKeys/DaemonKeyUpdateService.php @@ -25,21 +25,19 @@ namespace Pterodactyl\Services\DaemonKeys; use Carbon\Carbon; -use Pterodactyl\Models\DaemonKey; +use Webmozart\Assert\Assert; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; class DaemonKeyUpdateService { - const INTERNAL_TOKEN_IDENTIFIER = 'i_'; - /** * @var \Carbon\Carbon */ protected $carbon; /** - * @var + * @var \Illuminate\Contracts\Config\Repository */ protected $config; @@ -68,7 +66,7 @@ class DaemonKeyUpdateService /** * Update a daemon key to expire the previous one. * - * @param \Pterodactyl\Models\DaemonKey|int $key + * @param int $key * @return string * * @throws \RuntimeException @@ -77,15 +75,12 @@ class DaemonKeyUpdateService */ public function handle($key) { - if ($key instanceof DaemonKey) { - $key = $key->id; - } - - $secret = self::INTERNAL_TOKEN_IDENTIFIER . str_random(40); + Assert::integerish($key, 'First argument passed to handle must be an integer, received %s.'); + $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40); $this->repository->withoutFresh()->update($key, [ 'secret' => $secret, - 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time')), + 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time'))->toDateTimeString(), ]); return $secret; diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php index a00b0bda0..2b4929d4c 100644 --- a/app/Services/Nodes/NodeUpdateService.php +++ b/app/Services/Nodes/NodeUpdateService.php @@ -90,7 +90,7 @@ class NodeUpdateService $updateResponse = $this->repository->withoutFresh()->update($node->id, $data); try { - $this->configRepository->setNode($node->id)->setAccessToken($node->daemonSecret)->update(); + $this->configRepository->setNode($node->id)->update(); } catch (RequestException $exception) { $response = $exception->getResponse(); $this->writer->warning($exception); diff --git a/app/Services/Servers/DetailsModificationService.php b/app/Services/Servers/DetailsModificationService.php index 0e9a815af..a0c0f19c5 100644 --- a/app/Services/Servers/DetailsModificationService.php +++ b/app/Services/Servers/DetailsModificationService.php @@ -26,25 +26,36 @@ namespace Pterodactyl\Services\Servers; use Illuminate\Log\Writer; use Pterodactyl\Models\Server; -use Illuminate\Database\DatabaseManager; use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; +use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService; use Pterodactyl\Repositories\Daemon\ServerRepository as DaemonServerRepository; class DetailsModificationService { /** - * @var \Illuminate\Database\DatabaseManager + * @var \Illuminate\Database\ConnectionInterface */ - protected $database; + protected $connection; /** * @var \Pterodactyl\Repositories\Daemon\ServerRepository */ protected $daemonServerRepository; + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService + */ + protected $keyCreationService; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService + */ + private $keyDeletionService; + /** * @var \Pterodactyl\Repositories\Eloquent\ServerRepository */ @@ -58,19 +69,25 @@ class DetailsModificationService /** * DetailsModificationService constructor. * - * @param \Illuminate\Database\DatabaseManager $database - * @param \Pterodactyl\Repositories\Daemon\ServerRepository $daemonServerRepository - * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository - * @param \Illuminate\Log\Writer $writer + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService $keyDeletionService + * @param \Pterodactyl\Repositories\Daemon\ServerRepository $daemonServerRepository + * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository + * @param \Illuminate\Log\Writer $writer */ public function __construct( - DatabaseManager $database, + ConnectionInterface $connection, + DaemonKeyCreationService $keyCreationService, + DaemonKeyDeletionService $keyDeletionService, DaemonServerRepository $daemonServerRepository, ServerRepository $repository, Writer $writer ) { - $this->database = $database; + $this->connection = $connection; $this->daemonServerRepository = $daemonServerRepository; + $this->keyCreationService = $keyCreationService; + $this->keyDeletionService = $keyDeletionService; $this->repository = $repository; $this->writer = $writer; } @@ -80,7 +97,6 @@ class DetailsModificationService * * @param int|\Pterodactyl\Models\Server $server * @param array $data - * @return bool * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -92,46 +108,19 @@ class DetailsModificationService $server = $this->repository->find($server); } - $this->database->beginTransaction(); - $currentSecret = $server->daemonSecret; - - if ( - (isset($data['reset_token']) && ! is_null($data['reset_token'])) || - (isset($data['owner_id']) && $data['owner_id'] != $server->owner_id) - ) { - $data['daemonSecret'] = str_random(NodeCreationService::DAEMON_SECRET_LENGTH); - $shouldUpdate = true; - } - + $this->connection->beginTransaction(); $this->repository->withoutFresh()->update($server->id, [ 'owner_id' => array_get($data, 'owner_id') ?? $server->owner_id, 'name' => array_get($data, 'name') ?? $server->name, 'description' => array_get($data, 'description') ?? $server->description, - 'daemonSecret' => array_get($data, 'daemonSecret') ?? $server->daemonSecret, ], true, true); - // If there are no updates, lets save the changes and return. - if (! isset($shouldUpdate)) { - return $this->database->commit(); + if (array_get($data, 'owner_id') != $server->owner_id) { + $this->keyDeletionService->handle($server, $server->owner_id); + $this->keyCreationService->handle($server->id, array_get($data, 'owner_id')); } - try { - $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update([ - 'keys' => [ - (string) $currentSecret => [], - (string) $data['daemonSecret'] => $this->daemonServerRepository::DAEMON_PERMISSIONS, - ], - ]); - - return $this->database->commit(); - } catch (RequestException $exception) { - $response = $exception->getResponse(); - $this->writer->warning($exception); - - throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])); - } + $this->connection->commit(); } /** @@ -142,6 +131,7 @@ class DetailsModificationService * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function setDockerImage($server, $image) { @@ -149,7 +139,7 @@ class DetailsModificationService $server = $this->repository->find($server); } - $this->database->beginTransaction(); + $this->connection->beginTransaction(); $this->repository->withoutFresh()->update($server->id, ['image' => $image]); try { @@ -158,9 +148,8 @@ class DetailsModificationService 'image' => $image, ], ]); - - $this->database->commit(); } catch (RequestException $exception) { + $this->connection->rollBack(); $response = $exception->getResponse(); $this->writer->warning($exception); @@ -168,5 +157,7 @@ class DetailsModificationService 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), ])); } + + $this->connection->commit(); } } diff --git a/app/Services/Servers/ServerAccessHelperService.php b/app/Services/Servers/ServerAccessHelperService.php deleted file mode 100644 index 898f07d22..000000000 --- a/app/Services/Servers/ServerAccessHelperService.php +++ /dev/null @@ -1,131 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Services\Servers; - -use Carbon\Carbon; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\DaemonKey; -use Illuminate\Cache\Repository as CacheRepository; -use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService; -use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; -use Pterodactyl\Exceptions\Service\Server\UserNotLinkedToServerException; - -class ServerAccessHelperService -{ - /** - * @var \Illuminate\Cache\Repository - */ - protected $cache; - - /** - * @var \Carbon\Carbon - */ - protected $carbon; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - protected $daemonKeyRepository; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService - */ - protected $daemonKeyUpdateService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface - */ - protected $userRepository; - - /** - * ServerAccessHelperService constructor. - * - * @param \Illuminate\Cache\Repository $cache - * @param \Carbon\Carbon $carbon - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $daemonKeyRepository - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $daemonKeyUpdateService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository - */ - public function __construct( - CacheRepository $cache, - Carbon $carbon, - DaemonKeyRepositoryInterface $daemonKeyRepository, - DaemonKeyUpdateService $daemonKeyUpdateService, - ServerRepositoryInterface $repository, - UserRepositoryInterface $userRepository - ) { - $this->cache = $cache; - $this->carbon = $carbon; - $this->daemonKeyRepository = $daemonKeyRepository; - $this->daemonKeyUpdateService = $daemonKeyUpdateService; - $this->repository = $repository; - $this->userRepository = $userRepository; - } - - /** - * Return the daemon secret to use when making a connection. - * - * @param int|\Pterodactyl\Models\Server $server - * @param int|\Pterodactyl\Models\User $user - * @return string - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Server\UserNotLinkedToServerException - * @throws \RuntimeException - */ - public function handle($server, $user) - { - if (! $server instanceof Server) { - $server = $this->repository->find($server); - } - - if (! $user instanceof User) { - $user = $this->userRepository->find($user); - } - - $keys = $server->relationLoaded('keys') ? $server->keys : $this->daemonKeyRepository->getServerKeys($server->id); - $key = $keys->where('user_id', $user->root_admin ? $server->owner_id : $user->id)->first(); - - if (is_null($key)) { - throw new UserNotLinkedToServerException; - } - - if (max($this->carbon->now()->diffInSeconds($key->expires_at, false), 0) === 0) { - $key = $this->daemonKeyUpdateService->handle($key); - } - - return ($key instanceof DaemonKey) ? $key->secret : $key; - } -} diff --git a/app/Services/Subusers/PermissionCreationService.php b/app/Services/Subusers/PermissionCreationService.php index 8414e6c7c..9273dfa26 100644 --- a/app/Services/Subusers/PermissionCreationService.php +++ b/app/Services/Subusers/PermissionCreationService.php @@ -24,16 +24,12 @@ namespace Pterodactyl\Services\Subusers; +use Webmozart\Assert\Assert; use Pterodactyl\Models\Permission; use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; class PermissionCreationService { - const CORE_DAEMON_PERMISSIONS = [ - 's:get', - 's:console', - ]; - /** * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface */ @@ -54,21 +50,19 @@ class PermissionCreationService * * @param int $subuser * @param array $permissions - * @return array * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ public function handle($subuser, array $permissions) { + Assert::integerish($subuser, 'First argument passed to handle must be an integer, received %s.'); + $permissionMappings = Permission::getPermissions(true); - $daemonPermissions = self::CORE_DAEMON_PERMISSIONS; $insertPermissions = []; foreach ($permissions as $permission) { if (array_key_exists($permission, $permissionMappings)) { - if (! is_null($permissionMappings[$permission])) { - array_push($daemonPermissions, $permissionMappings[$permission]); - } + Assert::stringNotEmpty($permission, 'Permission argument provided must be a non-empty string, received %s.'); array_push($insertPermissions, [ 'subuser_id' => $subuser, @@ -77,8 +71,6 @@ class PermissionCreationService } } - $this->repository->insert($insertPermissions); - - return $daemonPermissions; + $this->repository->withoutFresh()->insert($insertPermissions); } } diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index 0bec408cb..2ee578b9e 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -24,20 +24,16 @@ namespace Pterodactyl\Services\Subusers; -use Illuminate\Log\Writer; use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException; use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class SubuserCreationService { @@ -47,9 +43,9 @@ class SubuserCreationService protected $connection; /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService */ - protected $daemonRepository; + protected $keyCreationService; /** * @var \Pterodactyl\Services\Subusers\PermissionCreationService @@ -76,41 +72,33 @@ class SubuserCreationService */ protected $userRepository; - /** - * @var \Illuminate\Log\Writer - */ - protected $writer; - /** * SubuserCreationService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository - * @param \Illuminate\Log\Writer $writer + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService + * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository */ public function __construct( ConnectionInterface $connection, UserCreationService $userCreationService, - DaemonServerRepositoryInterface $daemonRepository, + DaemonKeyCreationService $keyCreationService, PermissionCreationService $permissionService, ServerRepositoryInterface $serverRepository, SubuserRepositoryInterface $subuserRepository, - UserRepositoryInterface $userRepository, - Writer $writer + UserRepositoryInterface $userRepository ) { $this->connection = $connection; - $this->daemonRepository = $daemonRepository; + $this->keyCreationService = $keyCreationService; $this->permissionService = $permissionService; $this->subuserRepository = $subuserRepository; $this->serverRepository = $serverRepository; $this->userRepository = $userRepository; $this->userCreationService = $userCreationService; - $this->writer = $writer; } /** @@ -120,7 +108,6 @@ class SubuserCreationService * @return \Pterodactyl\Models\Subuser * * @throws \Exception - * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException @@ -154,28 +141,11 @@ class SubuserCreationService ]); } - $subuser = $this->subuserRepository->create([ - 'user_id' => $user->id, - 'server_id' => $server->id, - 'daemonSecret' => str_random(NodeCreationService::DAEMON_SECRET_LENGTH), - ]); + $subuser = $this->subuserRepository->create(['user_id' => $user->id, 'server_id' => $server->id]); + $this->keyCreationService->handle($server->id, $user->id); + $this->permissionService->handle($subuser->id, $permissions); + $this->connection->commit(); - $daemonPermissions = $this->permissionService->handle($subuser->id, $permissions); - - try { - $this->daemonRepository->setNode($server->node_id)->setAccessServer($server->uuid) - ->setSubuserKey($subuser->daemonSecret, $daemonPermissions); - $this->connection->commit(); - - return $subuser; - } catch (RequestException $exception) { - $this->connection->rollBack(); - $this->writer->warning($exception); - - $response = $exception->getResponse(); - throw new DisplayException(trans('exceptions.daemon_connection_failed', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])); - } + return $subuser; } } diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php index 97b723a50..76d472d76 100644 --- a/app/Services/Subusers/SubuserDeletionService.php +++ b/app/Services/Subusers/SubuserDeletionService.php @@ -24,12 +24,9 @@ namespace Pterodactyl\Services\Subusers; -use Illuminate\Log\Writer; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class SubuserDeletionService { @@ -39,45 +36,36 @@ class SubuserDeletionService protected $connection; /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService */ - protected $daemonRepository; + protected $keyDeletionService; /** * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface */ protected $repository; - /** - * @var \Illuminate\Log\Writer - */ - protected $writer; - /** * SubuserDeletionService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository - * @param \Illuminate\Log\Writer $writer + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService $keyDeletionService + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository */ public function __construct( ConnectionInterface $connection, - DaemonServerRepositoryInterface $daemonRepository, - SubuserRepositoryInterface $repository, - Writer $writer + DaemonKeyDeletionService $keyDeletionService, + SubuserRepositoryInterface $repository ) { $this->connection = $connection; - $this->daemonRepository = $daemonRepository; + $this->keyDeletionService = $keyDeletionService; $this->repository = $repository; - $this->writer = $writer; } /** * Delete a subuser and their associated permissions from the Panel and Daemon. * * @param int $subuser - * @return int|null * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException @@ -87,22 +75,8 @@ class SubuserDeletionService $subuser = $this->repository->getWithServer($subuser); $this->connection->beginTransaction(); - $response = $this->repository->delete($subuser->id); - - try { - $this->daemonRepository->setNode($subuser->server->node_id)->setAccessServer($subuser->server->uuid) - ->setSubuserKey($subuser->daemonSecret, []); - $this->connection->commit(); - - return $response; - } catch (RequestException $exception) { - $this->connection->rollBack(); - $this->writer->warning($exception); - - $response = $exception->getResponse(); - throw new DisplayException(trans('exceptions.daemon_connection_failed', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])); - } + $this->keyDeletionService->handle($subuser->server_id, $subuser->user_id); + $this->repository->delete($subuser->id); + $this->connection->commit(); } } diff --git a/app/Services/Subusers/SubuserUpdateService.php b/app/Services/Subusers/SubuserUpdateService.php index c11c551e9..b9294385c 100644 --- a/app/Services/Subusers/SubuserUpdateService.php +++ b/app/Services/Subusers/SubuserUpdateService.php @@ -28,6 +28,7 @@ use Illuminate\Log\Writer; use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; @@ -44,6 +45,11 @@ class SubuserUpdateService */ protected $daemonRepository; + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService + */ + private $keyProviderService; + /** * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface */ @@ -68,6 +74,7 @@ class SubuserUpdateService * SubuserUpdateService constructor. * * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $permissionRepository @@ -76,6 +83,7 @@ class SubuserUpdateService */ public function __construct( ConnectionInterface $connection, + DaemonKeyProviderService $keyProviderService, DaemonServerRepositoryInterface $daemonRepository, PermissionCreationService $permissionService, PermissionRepositoryInterface $permissionRepository, @@ -84,6 +92,7 @@ class SubuserUpdateService ) { $this->connection = $connection; $this->daemonRepository = $daemonRepository; + $this->keyProviderService = $keyProviderService; $this->permissionRepository = $permissionRepository; $this->permissionService = $permissionService; $this->repository = $repository; @@ -106,12 +115,11 @@ class SubuserUpdateService $this->connection->beginTransaction(); $this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]); - $daemonPermissions = $this->permissionService->handle($subuser->id, $permissions); + $this->permissionService->handle($subuser->id, $permissions); try { - $this->daemonRepository->setNode($subuser->server->node_id)->setAccessServer($subuser->server->uuid) - ->setSubuserKey($subuser->daemonSecret, $daemonPermissions); - $this->connection->commit(); + $token = $this->keyProviderService->handle($subuser->server_id, $subuser->user_id, false); + $this->daemonRepository->setNode($subuser->server->node_id)->revokeAccessKey($token); } catch (RequestException $exception) { $this->connection->rollBack(); $this->writer->warning($exception); @@ -121,5 +129,7 @@ class SubuserUpdateService 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), ])); } + + $this->connection->commit(); } } diff --git a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php index 4eb52db03..84cb2d92b 100644 --- a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php +++ b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; class RemoveDaemonSecretFromServersTable extends Migration { @@ -20,10 +21,10 @@ class RemoveDaemonSecretFromServersTable extends Migration $inserts[] = [ 'user_id' => $server->owner_id, 'server_id' => $server->id, - 'secret' => 'i_' . str_random(40), - 'expires_at' => Carbon::now()->addHours(24), - 'created_at' => Carbon::now(), - 'updated_at' => Carbon::now(), + 'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40), + 'expires_at' => Carbon::now()->addMinutes(config('pterodactyl.api.key_expire_time', 720))->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString(), ]; }); diff --git a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php index d2f6aaf7c..a0c5e6d10 100644 --- a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php +++ b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; class RemoveDaemonSecretFromSubusersTable extends Migration { @@ -18,10 +19,10 @@ class RemoveDaemonSecretFromSubusersTable extends Migration $inserts[] = [ 'user_id' => $subuser->user_id, 'server_id' => $subuser->server_id, - 'secret' => 'i_' . str_random(40), - 'expires_at' => Carbon::now()->addHours(24), - 'created_at' => Carbon::now(), - 'updated_at' => Carbon::now(), + 'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40), + 'expires_at' => Carbon::now()->addMinutes(config('pterodactyl.api.key_expire_time', 720))->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString(), ]; }); @@ -46,7 +47,7 @@ class RemoveDaemonSecretFromSubusersTable extends Migration $subusers = DB::table('subusers')->get(); $subusers->each(function ($subuser) { - DB::table('daemon_keys')->delete($subuser->id); + DB::table('daemon_keys')->where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); }); } } diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php index 304b604c5..886f7fb31 100644 --- a/resources/lang/en/command/messages.php +++ b/resources/lang/en/command/messages.php @@ -98,9 +98,9 @@ return [ 'using_redis' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.', 'redis_host' => 'Redis Host', 'redis_password' => 'Redis Password', + 'redis_pass_help' => 'By default a Redis server instance has no password as it is running locally and inaccessable to the outside world. If this is the case, simply hit enter without entering a value.', 'redis_port' => 'Redis Port', 'redis_pass_defined' => 'It seems a password is already defined for Redis, would you like to change it?', - 'redis_pass_help' => 'By default a Redis server instance has no password as it is running locally and inaccessable to the outside world. If this is the case, simply hit enter without entering a value.', ], ], ]; diff --git a/resources/themes/pterodactyl/base/index.blade.php b/resources/themes/pterodactyl/base/index.blade.php index 5e007972b..c0deceb3d 100644 --- a/resources/themes/pterodactyl/base/index.blade.php +++ b/resources/themes/pterodactyl/base/index.blade.php @@ -72,7 +72,7 @@