Merge branch 'develop' into feature/new-theme

# Conflicts:
#	.gitignore
#	public/themes/pterodactyl/js/frontend/files/filemanager.min.js
#	public/themes/pterodactyl/js/frontend/files/filemanager.min.js.map
#	public/themes/pterodactyl/js/frontend/serverlist.js
#	resources/themes/pterodactyl/server/files/list.blade.php
This commit is contained in:
Jakob Schrettenbrunner 2017-03-03 23:08:24 +01:00
commit 3a88deb97a
170 changed files with 9023 additions and 2222 deletions

View file

@ -13,7 +13,7 @@ DB_DATABASE=homestead
DB_USERNAME=homestead DB_USERNAME=homestead
DB_PASSWORD=secret DB_PASSWORD=secret
CACHE_DRIVER=file CACHE_DRIVER=memcached
SESSION_DRIVER=database SESSION_DRIVER=database
MAIL_DRIVER=smtp MAIL_DRIVER=smtp

1
.gitignore vendored
View file

@ -11,3 +11,4 @@ Vagrantfile
node_modules node_modules
yarn.lock yarn.lock
node_modules

View file

@ -3,23 +3,66 @@ This file is a running track of new features and fixes to each version of the pa
This project follows [Semantic Versioning](http://semver.org) guidelines. This project follows [Semantic Versioning](http://semver.org) guidelines.
## v0.6.0-pre.1 ## v0.6.0-pre.4 (Courageous Carniadactylus)
### Fixed
* `[pre.3]` — Fixes bug in cache handler that doesn't cache against the user making the request. Would have allowed for users to access servers not belonging to themselves in production.
* `[pre.3]` — Fixes misnamed MySQL column that was causing the inability to delete certain port ranges from the database.
* `[pre.3]` — Fixes bug preventing rebuilding server containers through the Admin CP.
### Added
* New cache policy for ServerPolicy to avoid making 15+ queries per page load when confirming if a user has permission to perform an action.
## v0.6.0-pre.3 (Courageous Carniadactylus)
### Fixed
* `[pre.2]` — Fixes bug where servers could not be manually deployed to nodes due to a broken SQL call.
* `[pre.2]` — Fixes inability to edit a server due to owner_id issues.
* `[pre.2]` — Fixes bug when trying to add new subusers.
* Emails sending with 'Pterodactyl Panel' as the from name. Now configurable by using `php artisan pterodactyl:mail` to update.
* `[pre.2]` — Fixes inability to delete accounts due to SQL changes.
* `[pre.2]` — Fixes bug with checking power-permissions that showed the wrong buttons. Also adds check back to sidebar to only show options a user can use.
* `[pre.2]` — Fixes allocation listing on node allocations tab as well as bug preventing deletion of port.
* `[pre.2]` — Fixes bug in services that prevented saving updated settings or creating new services.
### Changed
* `[pre.2]` — File Manager now displays relevant information on all screen sizes, and includes better button clicking mechanics for dropdown menu.
* Reduced the number of database queries being executed when viewing a specific server. This is done by caching the query for up to 60 minutes in memcached.
* User creation emails include more information and are sent by the event listener rather than the repository.
* Account password reset emails now auto-fill the email when clicking the link.
### Added
* Notifications when a user is added or removed as a subuser for a server.
## v0.6.0-pre.2 (Courageous Carniadactylus)
### Fixed
* `[pre.1]` — Fixes bug with database seeders that prevented correctly installing the panel.
### Changed
* `[pre.1]` — Moved around navigation bar on fronted to make it more obvious where logout and admin buttons were, as well as use the right icon for server listing.
## v0.6.0-pre.1 (Courageous Carniadactylus)
### Added ### Added
* Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon. * Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon.
* Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update. * Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update.
* Users can now have a username as well as client name assigned to their account. * Users can now have a username as well as client name assigned to their account.
* Ability to create a node through the CLI using `pterodactyl:node` as well as locations via `pterodactyl:location`. * Ability to create a node through the CLI using `pterodactyl:node` as well as locations via `pterodactyl:location`.
* New theme (AdminLTE) for front-end with tweaks to backend files to work properly with it. * New theme (AdminLTE) for front-end with tweaks to backend files to work properly with it.
* Add support for PhraseApp's in-context editor
### Fixed ### Fixed
* Bug causing error logs to be spammed if someone timed out on an ajax based page. * Bug causing error logs to be spammed if someone timed out on an ajax based page.
* Fixes edge case where specific server names could cause daemon errors due to an invalid SFTP username being created by the panel. * Fixes edge case where specific server names could cause daemon errors due to an invalid SFTP username being created by the panel.
* Fixes sessions being removed on browser close, and set sessions to idle for up to 3 hours before being marked as expired.
### Changed ### Changed
* Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields. * Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields.
* User model now defines mass assignment fields using `$fillable` rather than `$guarded`. * User model now defines mass assignment fields using `$fillable` rather than `$guarded`.
* 2FA checkpoint on login is now its own page, and not an AJAX based call. Improves security on that front.
* Updated Server model code to be more efficient, as well as make life easier for backend changes and work.
### Deprecated ### Deprecated
* `Server::getUserDaemonSecret(Server $server)` was removed and replaced with `User::daemonSecret(Server $server)` in order to clean up models.
* `Server::getByUUID()` was replaced with `Server::byUuid()` as well as various other functions through-out the Server model.
* `Server::getHeaders()` was removed and replaced with `Server::getClient()` which returns a Guzzle Client with the correct headers already assigned.
## v0.5.6 (Bodacious Boreopterus) ## v0.5.6 (Bodacious Boreopterus)
### Added ### Added

View file

@ -1,12 +1,14 @@
[![Logo Image](https://cdn.pterodactyl.io/logos/Banner%20Logo%20Black@2x.png)](https://pterodactyl.io)
## Pterodactyl Panel ## Pterodactyl Panel
Pterodactyl is the free game server management panel designed by users, for users. Featuring support for Vanilla Minecraft, Spigot, Source Dedicated Servers, BungeeCord, and many more. Pterodactyl is built on the `Laravel PHP Framework (v5.3)`. Pterodactyl Panel is the free, open-source, game agnostic, self-hosted control panel for users, networks, and game service providers. Pterodactyl supports games and servers such as Minecraft (including Spigot, Bungeecord, and Sponge), ARK: Evolution Evolved, CS:GO, Team Fortress 2, Insurgency, Teamspeak 3, Mumble, and many more. Control all of your games from one unified interface.
## Support & Documentation ## Support & Documentation
Support for using Pterodactyl can be found on our [wiki](https://github.com/Pterodactyl/Panel/wiki) or on our [Discord chat](https://discord.gg/0gYt8oU8QOkDhKLS). Support for using Pterodactyl can be found on our [Documentation Website](https://docs.pterodactyl.io), our [Discord Chat](https://discord.gg/QRDZvVm), or via our [Forums](https://forums.pterodactyl.io).
## License ## License
``` ```
Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -39,8 +41,6 @@ Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICEN
Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/) Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/)
BinaryJS - [license](https://github.com/binaryjs/binaryjs/blob/master/LICENSE) - [homepage](http://binaryjs.com)
Bootstrap - [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) - [homepage](http://getbootstrap.com) Bootstrap - [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) - [homepage](http://getbootstrap.com)
BootStrap Notify - [license](https://github.com/mouse0270/bootstrap-notify/blob/master/LICENSE) - [homepage](http://bootstrap-notify.remabledesigns.com) BootStrap Notify - [license](https://github.com/mouse0270/bootstrap-notify/blob/master/LICENSE) - [homepage](http://bootstrap-notify.remabledesigns.com)
@ -51,12 +51,12 @@ FontAwesome - [license](http://fontawesome.io/license/) - [homepage](http://font
FontAwesome Animations - [license](https://github.com/l-lin/font-awesome-animation#license) - [homepage](https://github.com/l-lin/font-awesome-animation) FontAwesome Animations - [license](https://github.com/l-lin/font-awesome-animation#license) - [homepage](https://github.com/l-lin/font-awesome-animation)
FuelUX - [license](https://github.com/ExactTarget/fuelux/blob/master/LICENSE) - [homepage](http://getfuelux.com)
jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [homepage](http://jquery.com) jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [homepage](http://jquery.com)
jQuery Terminal - [license](https://github.com/jcubic/jquery.terminal/blob/master/LICENSE) - [homepage](http://terminal.jcubic.pl) jQuery Terminal - [license](https://github.com/jcubic/jquery.terminal/blob/master/LICENSE) - [homepage](http://terminal.jcubic.pl)
Laravel Framework - [license](https://github.com/laravel/framework/blob/5.4/LICENSE.md) - [homepage](https://laravel.com)
Lodash - [license](https://github.com/lodash/lodash/blob/master/LICENSE) - [homepage](https://lodash.com/) Lodash - [license](https://github.com/lodash/lodash/blob/master/LICENSE) - [homepage](https://lodash.com/)
Select2 - [license](https://github.com/select2/select2/blob/master/LICENSE.md) - [homepage](https://select2.github.io) Select2 - [license](https://github.com/select2/select2/blob/master/LICENSE.md) - [homepage](https://select2.github.io)
@ -74,6 +74,6 @@ Some Javascript and CSS used within the panel is licensed under a `MIT` or `Apac
Some images used within Pterodactyl are Copyright (c) their respective owners. Some images used within Pterodactyl are Copyright (c) their respective owners.
`/public/images/403.jpg` is licensed under a [CC BY 2.0](http://creativecommons.org/licenses/by/2.0/) by [BigTallGuy](http://flickr.com/photos/bigtallguy/) `/public/themes/default/images/403.jpg` is licensed under a [CC BY 2.0](http://creativecommons.org/licenses/by/2.0/) by [BigTallGuy](http://flickr.com/photos/bigtallguy/)
`/public/images/404.jpg` is licensed under a [CC BY-SA 2.0](http://creativecommons.org/licenses/by-sa/2.0/) by [nicsuzor](http://flickr.com/photos/nicsuzor/) `/public/themes/default/images/404.jpg` is licensed under a [CC BY-SA 2.0](http://creativecommons.org/licenses/by-sa/2.0/) by [nicsuzor](http://flickr.com/photos/nicsuzor/)

View file

@ -36,6 +36,7 @@ class UpdateEmailSettings extends Command
protected $signature = 'pterodactyl:mail protected $signature = 'pterodactyl:mail
{--driver=} {--driver=}
{--email=} {--email=}
{--from-name=}
{--host=} {--host=}
{--port=} {--port=}
{--username=} {--username=}
@ -137,6 +138,7 @@ class UpdateEmailSettings extends Command
} }
$variables['MAIL_FROM'] = is_null($this->option('email')) ? $this->ask('Email address emails should originate from') : $this->option('email'); $variables['MAIL_FROM'] = is_null($this->option('email')) ? $this->ask('Email address emails should originate from') : $this->option('email');
$variables['MAIL_FROM_NAME'] = is_null($this->option('from-name')) ? $this->ask('Name emails should appear to be from') : $this->option('from-name');
$variables['MAIL_ENCRYPTION'] = 'tls'; $variables['MAIL_ENCRYPTION'] = 'tls';
$bar = $this->output->createProgressBar(count($variables)); $bar = $this->output->createProgressBar(count($variables));
@ -155,6 +157,9 @@ class UpdateEmailSettings extends Command
file_put_contents($file, $envContents); file_put_contents($file, $envContents);
$bar->finish(); $bar->finish();
$this->line('Updating evironment configuration cache file.');
$this->call('config:cache');
echo "\n"; echo "\n";
} }
} }

View file

@ -134,6 +134,10 @@ class UpdateEnvironment extends Command
$variables['APP_TIMEZONE'] = $this->option('timezone'); $variables['APP_TIMEZONE'] = $this->option('timezone');
} }
$variables['APP_THEME'] = 'pterodactyl';
$variables['CACHE_DRIVER'] = 'memcached';
$variables['SESSION_DRIVER'] = 'database';
$bar = $this->output->createProgressBar(count($variables)); $bar = $this->output->createProgressBar(count($variables));
$this->line('Writing new environment configuration to file.'); $this->line('Writing new environment configuration to file.');
@ -150,6 +154,9 @@ class UpdateEnvironment extends Command
file_put_contents($file, $envContents); file_put_contents($file, $envContents);
$bar->finish(); $bar->finish();
$this->line('Updating evironment configuration cache file.');
$this->call('config:cache');
echo "\n"; echo "\n";
} }
} }

View file

@ -22,42 +22,24 @@
* SOFTWARE. * SOFTWARE.
*/ */
namespace Pterodactyl\Models; namespace Pterodactyl\Events\Server;
use Illuminate\Database\Eloquent\Model; use Pterodactyl\Models\Server;
use Illuminate\Queue\SerializesModels;
class ServiceOptions extends Model class Created
{ {
/** use SerializesModels;
* The table associated with the model.
* public $server;
* @var string
*/
protected $table = 'service_options';
/** /**
* Fields that are not mass assignable. * Create a new event instance.
* *
* @var array * @return void
*/ */
protected $guarded = ['id', 'created_at', 'updated_at']; public function __construct(Server $server)
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'parent_service' => 'integer',
];
/**
* Gets all variables associated with this service.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function variables()
{ {
return $this->hasMany(ServiceVariables::class, 'option_id'); $this->server = $server;
} }
} }

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Server;
use Pterodactyl\Models\Server;
use Illuminate\Queue\SerializesModels;
class Creating
{
use SerializesModels;
public $server;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Server $server)
{
$this->server = $server;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Server;
use Pterodactyl\Models\Server;
use Illuminate\Queue\SerializesModels;
class Deleted
{
use SerializesModels;
public $server;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Server $server)
{
$this->server = $server;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Server;
use Pterodactyl\Models\Server;
use Illuminate\Queue\SerializesModels;
class Deleting
{
use SerializesModels;
public $server;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Server $server)
{
$this->server = $server;
}
}

View file

@ -22,11 +22,12 @@
* SOFTWARE. * SOFTWARE.
*/ */
namespace Pterodactyl\Events; namespace Pterodactyl\Events\Server;
use Pterodactyl\Models\Server;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ServerDeleted class Saved
{ {
use SerializesModels; use SerializesModels;
@ -37,8 +38,8 @@ class ServerDeleted
* *
* @return void * @return void
*/ */
public function __construct($id) public function __construct(Server $server)
{ {
$this->server = $id; $this->server = $server;
} }
} }

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Server;
use Pterodactyl\Models\Server;
use Illuminate\Queue\SerializesModels;
class Saving
{
use SerializesModels;
public $server;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Server $server)
{
$this->server = $server;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Server;
use Pterodactyl\Models\Server;
use Illuminate\Queue\SerializesModels;
class Updated
{
use SerializesModels;
public $server;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Server $server)
{
$this->server = $server;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Server;
use Pterodactyl\Models\Server;
use Illuminate\Queue\SerializesModels;
class Updating
{
use SerializesModels;
public $server;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Server $server)
{
$this->server = $server;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Subuser;
use Pterodactyl\Models\Subuser;
use Illuminate\Queue\SerializesModels;
class Created
{
use SerializesModels;
public $subuser;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Subuser $subuser)
{
$this->subuser = $subuser;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Subuser;
use Pterodactyl\Models\Subuser;
use Illuminate\Queue\SerializesModels;
class Creating
{
use SerializesModels;
public $subuser;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Subuser $subuser)
{
$this->subuser = $subuser;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Subuser;
use Pterodactyl\Models\Subuser;
use Illuminate\Queue\SerializesModels;
class Deleted
{
use SerializesModels;
public $subuser;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Subuser $subuser)
{
$this->subuser = $subuser;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\Subuser;
use Pterodactyl\Models\Subuser;
use Illuminate\Queue\SerializesModels;
class Deleting
{
use SerializesModels;
public $subuser;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Subuser $subuser)
{
$this->subuser = $subuser;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\User;
use Pterodactyl\Models\User;
use Illuminate\Queue\SerializesModels;
class Created
{
use SerializesModels;
public $user;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\User;
use Pterodactyl\Models\User;
use Illuminate\Queue\SerializesModels;
class Creating
{
use SerializesModels;
public $user;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\User;
use Pterodactyl\Models\User;
use Illuminate\Queue\SerializesModels;
class Deleted
{
use SerializesModels;
public $user;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Events\User;
use Pterodactyl\Models\User;
use Illuminate\Queue\SerializesModels;
class Deleting
{
use SerializesModels;
public $user;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Extensions;
use Illuminate\Translation\Translator as LaravelTranslator;
class PhraseAppTranslator extends LaravelTranslator
{
/**
* Get the translation for the given key.
*
* @param string $key
* @param array $replace
* @param string|null $locale
* @param bool $fallback
* @return string|array|null
*/
public function get($key, array $replace = [], $locale = null, $fallback = true)
{
$key = substr($key, strpos($key, '.') + 1);
return "{{__phrase_${key}__}}";
}
}

View file

@ -24,7 +24,6 @@
namespace Pterodactyl\Http\Controllers\API; namespace Pterodactyl\Http\Controllers\API;
use DB;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\Location; use Pterodactyl\Models\Location;
@ -49,11 +48,12 @@ class LocationController extends BaseController
*/ */
public function lists(Request $request) public function lists(Request $request)
{ {
return Location::select('locations.*', DB::raw('GROUP_CONCAT(nodes.id) as nodes')) return Location::with('nodes')->get()->map(function ($item) {
->join('nodes', 'locations.id', '=', 'nodes.location') $item->nodes->transform(function ($item) {
->groupBy('locations.id') return collect($item)->only(['id', 'name', 'fqdn', 'scheme', 'daemonListen', 'daemonSFTP']);
->get()->each(function ($location) { });
$location->nodes = explode(',', $location->nodes);
})->all(); return $item;
})->toArray();
} }
} }

View file

@ -24,6 +24,7 @@
namespace Pterodactyl\Http\Controllers\API; namespace Pterodactyl\Http\Controllers\API;
use Log;
use Pterodactyl\Models; use Pterodactyl\Models;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Dingo\Api\Exception\ResourceException; use Dingo\Api\Exception\ResourceException;
@ -96,15 +97,21 @@ class NodeController extends BaseController
public function create(Request $request) public function create(Request $request)
{ {
try { try {
$node = new NodeRepository; $repo = new NodeRepository;
$new = $node->create($request->all()); $node = $repo->create($request->only([
'name', 'location_id', 'public', 'fqdn',
'scheme', 'memory', 'memory_overallocate',
'disk', 'disk_overallocate', 'daemonBase',
'daemonSFTP', 'daemonListen',
]));
return ['id' => $new]; return ['id' => $repo->id];
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true));
} catch (DisplayException $ex) { } catch (DisplayException $ex) {
throw new ResourceException($ex->getMessage()); throw new ResourceException($ex->getMessage());
} catch (\Exception $e) { } catch (\Exception $ex) {
Log::error($ex);
throw new BadRequestHttpException('There was an error while attempting to add this node to the system.'); throw new BadRequestHttpException('There was an error while attempting to add this node to the system.');
} }
} }
@ -124,88 +131,35 @@ class NodeController extends BaseController
*/ */
public function view(Request $request, $id, $fields = null) public function view(Request $request, $id, $fields = null)
{ {
$node = Models\Node::where('id', $id); $node = Models\Node::with('allocations')->where('id', $id)->first();
if (! $node) {
if (! is_null($request->input('fields'))) {
foreach (explode(',', $request->input('fields')) as $field) {
if (! empty($field)) {
$node->addSelect($field);
}
}
}
try {
if (! $node->first()) {
throw new NotFoundHttpException('No node by that ID was found.'); throw new NotFoundHttpException('No node by that ID was found.');
} }
return [ $node->allocations->transform(function ($item) {
'node' => $node->first(), return collect($item)->only([
'allocations' => [ 'id', 'ip', 'ip_alias', 'port', 'server_id',
'assigned' => Models\Allocation::where('node', $id)->whereNotNull('assigned_to')->get(), ]);
'unassigned' => Models\Allocation::where('node', $id)->whereNull('assigned_to')->get(), });
],
]; if (! is_null($request->input('fields'))) {
} catch (NotFoundHttpException $ex) { $fields = explode(',', $request->input('fields'));
throw $ex; if (! empty($fields) && is_array($fields)) {
} catch (\Exception $ex) { return collect($node)->only($fields);
throw new BadRequestHttpException('There was an issue with the fields passed in the request.');
} }
} }
return $node;
}
public function config(Request $request, $id) public function config(Request $request, $id)
{ {
if (! $request->secure()) {
throw new BadRequestHttpException('This API route can only be accessed using a secure connection.');
}
$node = Models\Node::where('id', $id)->first(); $node = Models\Node::where('id', $id)->first();
if (! $node) { if (! $node) {
throw new NotFoundHttpException('No node by that ID was found.'); throw new NotFoundHttpException('No node by that ID was found.');
} }
return [ return $node->getConfigurationAsJson();
'web' => [
'listen' => $node->daemonListen,
'host' => '0.0.0.0',
'ssl' => [
'enabled' => ($node->scheme === 'https'),
'certificate' => '/etc/certs/' . $node->fqdn . '/fullchain.pem',
'key' => '/etc/certs/' . $node->fqdn . '/privkey.pem',
],
],
'docker' => [
'socket' => '/var/run/docker.sock',
'autoupdate_images' => true,
],
'sftp' => [
'path' => $node->daemonBase,
'port' => (int) $node->daemonSFTP,
'container' => 'ptdl-sftp',
],
'query' => [
'kill_on_fail' => true,
'fail_limit' => 5,
],
'logger' => [
'path' => 'logs/',
'src' => false,
'level' => 'info',
'period' => '1d',
'count' => 3,
],
'remote' => [
'base' => config('app.url'),
'download' => route('remote.download'),
'installed' => route('remote.install'),
],
'uploads' => [
'size_limit' => $node->upload_size,
],
'keys' => [
$node->daemonSecret,
],
];
} }
/** /**
@ -219,12 +173,7 @@ class NodeController extends BaseController
*/ */
public function allocations(Request $request) public function allocations(Request $request)
{ {
$allocations = Models\Allocation::all(); return Models\Allocation::all()->toArray();
if ($allocations->count() < 1) {
throw new NotFoundHttpException('No allocations have been created.');
}
return $allocations;
} }
/** /**
@ -238,18 +187,7 @@ class NodeController extends BaseController
*/ */
public function allocationsView(Request $request, $id) public function allocationsView(Request $request, $id)
{ {
$query = Models\Allocation::where('assigned_to', $id)->get(); return Models\Allocation::where('server_id', $id)->get()->toArray();
try {
if (empty($query)) {
throw new NotFoundHttpException('No allocations for that server were found.');
}
return $query;
} catch (NotFoundHttpException $ex) {
throw $ex;
} catch (\Exception $ex) {
throw new BadRequestHttpException('There was an issue with the fields passed in the request.');
}
} }
/** /**

View file

@ -72,10 +72,10 @@ class ServerController extends BaseController
public function create(Request $request) public function create(Request $request)
{ {
try { try {
$server = new ServerRepository; $repo = new ServerRepository;
$new = $server->create($request->all()); $server = $repo->create($request->all());
return ['id' => $new]; return ['id' => $server->id];
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true));
} catch (DisplayException $ex) { } catch (DisplayException $ex) {
@ -101,55 +101,38 @@ class ServerController extends BaseController
*/ */
public function view(Request $request, $id) public function view(Request $request, $id)
{ {
$query = Models\Server::where('id', $id); $server = Models\Server::with('node', 'allocations', 'pack')->where('id', $id)->first();
if (! $server) {
if (! is_null($request->input('fields'))) {
foreach (explode(',', $request->input('fields')) as $field) {
if (! empty($field)) {
$query->addSelect($field);
}
}
}
try {
if (! $query->first()) {
throw new NotFoundHttpException('No server by that ID was found.'); throw new NotFoundHttpException('No server by that ID was found.');
} }
// Requested Daemon Stats if (! is_null($request->input('fields'))) {
$server = $query->first(); $fields = explode(',', $request->input('fields'));
if (! empty($fields) && is_array($fields)) {
return collect($server)->only($fields);
}
}
if ($request->input('daemon') === 'true') { if ($request->input('daemon') === 'true') {
$node = Models\Node::findOrFail($server->node); try {
$client = Models\Node::guzzleRequest($node->id); $response = $server->node->guzzleClient([
'X-Access-Token' => $server->node->daemonSecret,
])->request('GET', '/servers');
$response = $client->request('GET', '/servers', [
'headers' => [
'X-Access-Token' => $node->daemonSecret,
],
]);
// Only return the daemon token if the request is using HTTPS
if ($request->secure()) {
$server->daemon_token = $server->daemonSecret;
}
$server->daemon = json_decode($response->getBody())->{$server->uuid}; $server->daemon = json_decode($response->getBody())->{$server->uuid};
return $server->toArray();
}
return $server->toArray();
} catch (NotFoundHttpException $ex) {
throw $ex;
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (\GuzzleHttp\Exception\TransferException $ex) {
// Couldn't hit the daemon, return what we have though. // Couldn't hit the daemon, return what we have though.
$server->daemon = [ $server->daemon = [
'error' => 'There was an error encountered while attempting to connect to the remote daemon.', 'error' => 'There was an error encountered while attempting to connect to the remote daemon.',
]; ];
}
}
$server->allocations->transform(function ($item) {
return collect($item)->except(['created_at', 'updated_at']);
});
return $server->toArray(); return $server->toArray();
} catch (\Exception $ex) {
throw new BadRequestHttpException('There was an issue with the fields passed in the request.');
}
} }
/** /**
@ -176,7 +159,9 @@ class ServerController extends BaseController
{ {
try { try {
$server = new ServerRepository; $server = new ServerRepository;
$server->updateDetails($id, $request->all()); $server->updateDetails($id, $request->only([
'owner', 'name', 'reset_token',
]));
return Models\Server::findOrFail($id); return Models\Server::findOrFail($id);
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
@ -221,7 +206,10 @@ class ServerController extends BaseController
{ {
try { try {
$server = new ServerRepository; $server = new ServerRepository;
$server->changeBuild($id, $request->all()); $server->changeBuild($id, $request->only([
'default', 'add_additional', 'remove_additional',
'memory', 'swap', 'io', 'cpu', 'disk',
]));
return Models\Server::findOrFail($id); return Models\Server::findOrFail($id);
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {

View file

@ -45,17 +45,11 @@ class ServiceController extends BaseController
public function view(Request $request, $id) public function view(Request $request, $id)
{ {
$service = Models\Service::find($id); $service = Models\Service::with('options.variables', 'options.packs')->find($id);
if (! $service) { if (! $service) {
throw new NotFoundHttpException('No service by that ID was found.'); throw new NotFoundHttpException('No service by that ID was found.');
} }
return [ return $service->toArray();
'service' => $service,
'options' => Models\ServiceOptions::select('id', 'name', 'description', 'tag', 'docker_image')
->where('parent_service', $service->id)
->with('variables')
->get(),
];
} }
} }

View file

@ -24,7 +24,6 @@
namespace Pterodactyl\Http\Controllers\API\User; namespace Pterodactyl\Http\Controllers\API\User;
use Pterodactyl\Models;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Http\Controllers\API\BaseController; use Pterodactyl\Http\Controllers\API\BaseController;
@ -32,19 +31,16 @@ class InfoController extends BaseController
{ {
public function me(Request $request) public function me(Request $request)
{ {
return Models\Server::getUserServers()->map(function ($server) { return $request->user()->serverAccessCollection()->load('allocation', 'option')->map(function ($server) {
return [ return [
'id' => $server->uuidShort, 'id' => $server->uuidShort,
'uuid' => $server->uuid, 'uuid' => $server->uuid,
'name' => $server->name, 'name' => $server->name,
'node' => $server->nodeName, 'node' => $server->node->name,
'ip' => [ 'ip' => $server->allocation->alias,
'set' => $server->ip, 'port' => $server->allocation->port,
'alias' => $server->ip_alias, 'service' => $server->service->name,
], 'option' => $server->option->name,
'port' => $server->port,
'service' => $server->a_serviceName,
'option' => $server->a_serviceOptionName,
]; ];
})->all(); })->all();
} }

View file

@ -25,7 +25,6 @@
namespace Pterodactyl\Http\Controllers\API\User; namespace Pterodactyl\Http\Controllers\API\User;
use Log; use Log;
use Auth;
use Pterodactyl\Models; use Pterodactyl\Models;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Http\Controllers\API\BaseController; use Pterodactyl\Http\Controllers\API\BaseController;
@ -34,42 +33,28 @@ class ServerController extends BaseController
{ {
public function info(Request $request, $uuid) public function info(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid)->load('allocations');
$node = Models\Node::findOrFail($server->node);
$client = Models\Node::guzzleRequest($node->id);
try { try {
$response = $client->request('GET', '/server', [ $response = $server->guzzleClient()->request('GET', '/server');
'headers' => [
'X-Access-Token' => $server->daemonSecret,
'X-Access-Server' => $server->uuid,
],
]);
$json = json_decode($response->getBody()); $json = json_decode($response->getBody());
$daemon = [ $daemon = [
'status' => $json->status, 'status' => $json->status,
'stats' => $json->proc, 'stats' => $json->proc,
'query' => $json->query,
]; ];
} catch (\Exception $ex) { } catch (\Exception $ex) {
$daemon = [ $daemon = [
'error' => 'An error was encountered while trying to connect to the daemon to collece information. It might be offline.', 'error' => 'An error was encountered while trying to connect to the daemon to collect information. It might be offline.',
]; ];
Log::error($ex); Log::error($ex);
} }
$allocations = Models\Allocation::select('id', 'ip', 'port', 'ip_alias as alias')->where('assigned_to', $server->id)->get();
foreach ($allocations as &$allocation) {
$allocation->default = ($allocation->id === $server->allocation);
unset($allocation->id);
}
return [ return [
'uuidShort' => $server->uuidShort, 'uuidShort' => $server->uuidShort,
'uuid' => $server->uuid, 'uuid' => $server->uuid,
'name' => $server->name, 'name' => $server->name,
'node' => $node->name, 'node' => $server->node->name,
'limits' => [ 'limits' => [
'memory' => $server->memory, 'memory' => $server->memory,
'swap' => $server->swap, 'swap' => $server->swap,
@ -78,12 +63,18 @@ class ServerController extends BaseController
'cpu' => $server->cpu, 'cpu' => $server->cpu,
'oom_disabled' => (bool) $server->oom_disabled, 'oom_disabled' => (bool) $server->oom_disabled,
], ],
'allocations' => $allocations, 'allocations' => $server->allocations->map(function ($item) use ($server) {
return [
'ip' => $item->alias,
'port' => $item->port,
'default' => ($item->id === $server->allocation_id),
];
}),
'sftp' => [ 'sftp' => [
'username' => (Auth::user()->can('view-sftp', $server)) ? $server->username : null, 'username' => ($request->user()->can('view-sftp', $server)) ? $server->username : null,
], ],
'daemon' => [ 'daemon' => [
'token' => ($request->secure()) ? $server->daemonSecret : false, 'token' => $server->daemonSecret,
'response' => $daemon, 'response' => $daemon,
], ],
]; ];
@ -91,16 +82,10 @@ class ServerController extends BaseController
public function power(Request $request, $uuid) public function power(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$client = Models\Node::guzzleRequest($server->node);
Auth::user()->can('power-' . $request->input('action'), $server); Auth::user()->can('power-' . $request->input('action'), $server);
$res = $client->request('PUT', '/server/power', [ $res = $server->guzzleClient()->request('PUT', '/server/power', [
'headers' => [
'X-Access-Server' => $server->uuid,
'X-Access-Token' => $server->daemonSecret,
],
'exceptions' => false, 'exceptions' => false,
'json' => [ 'json' => [
'action' => $request->input('action'), 'action' => $request->input('action'),

View file

@ -31,7 +31,6 @@ use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Repositories\UserRepository; use Pterodactyl\Repositories\UserRepository;
use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Exceptions\DisplayValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
/** /**
@ -75,33 +74,29 @@ class UserController extends BaseController
*/ */
public function view(Request $request, $id) public function view(Request $request, $id)
{ {
$query = Models\User::where((is_numeric($id) ? 'id' : 'email'), $id); $user = Models\User::with('servers')->where((is_numeric($id) ? 'id' : 'email'), $id)->first();
if (! $user->first()) {
if (! is_null($request->input('fields'))) {
foreach (explode(',', $request->input('fields')) as $field) {
if (! empty($field)) {
$query->addSelect($field);
}
}
}
try {
if (! $query->first()) {
throw new NotFoundHttpException('No user by that ID was found.'); throw new NotFoundHttpException('No user by that ID was found.');
} }
$user = $query->first(); $user->servers->transform(function ($item) {
$userArray = $user->toArray(); return collect($item)->only([
$userArray['servers'] = Models\Server::select('id', 'uuid', 'node', 'suspended')->where('owner', $user->id)->get(); 'id', 'node_id', 'uuidShort',
'uuid', 'name', 'suspended',
'owner_id',
]);
});
return $userArray; if (! is_null($request->input('fields'))) {
} catch (NotFoundHttpException $ex) { $fields = explode(',', $request->input('fields'));
throw $ex; if (! empty($fields) && is_array($fields)) {
} catch (\Exception $ex) { return collect($user)->only($fields);
throw new BadRequestHttpException('There was an issue with the fields passed in the request.');
} }
} }
return $user->toArray();
}
/** /**
* Create a New User. * Create a New User.
* *
@ -123,7 +118,9 @@ class UserController extends BaseController
try { try {
$user = new UserRepository; $user = new UserRepository;
$create = $user->create($request->only([ $create = $user->create($request->only([
'email', 'username', 'name_first', 'name_last', 'password', 'root_admin', 'custom_id', 'email', 'username', 'name_first',
'name_last', 'password',
'root_admin', 'custom_id',
])); ]));
$create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id')); $create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id'));
@ -160,7 +157,9 @@ class UserController extends BaseController
try { try {
$user = new UserRepository; $user = new UserRepository;
$user->update($id, $request->only([ $user->update($id, $request->only([
'username', 'email', 'name_first', 'name_last', 'password', 'root_admin', 'language', 'username', 'email', 'name_first',
'name_last', 'password',
'root_admin', 'language',
])); ]));
return Models\User::findOrFail($id); return Models\User::findOrFail($id);

View file

@ -24,7 +24,6 @@
namespace Pterodactyl\Http\Controllers\Admin; namespace Pterodactyl\Http\Controllers\Admin;
use DB;
use Log; use Log;
use Alert; use Alert;
use Pterodactyl\Models; use Pterodactyl\Models;
@ -47,30 +46,15 @@ class DatabaseController extends Controller
public function getIndex(Request $request) public function getIndex(Request $request)
{ {
return view('admin.databases.index', [ return view('admin.databases.index', [
'databases' => Models\Database::select( 'databases' => Models\Database::with('server')->paginate(50),
'databases.*', 'hosts' => Models\DatabaseServer::withCount('databases')->with('node')->paginate(20),
'database_servers.host as a_host',
'database_servers.port as a_port',
'servers.id as a_serverId',
'servers.name as a_serverName'
)->join('database_servers', 'database_servers.id', '=', 'databases.db_server')
->join('servers', 'databases.server_id', '=', 'servers.id')
->paginate(20),
'dbh' => Models\DatabaseServer::select(
'database_servers.*',
'nodes.name as a_linkedNode',
DB::raw('(SELECT COUNT(*) FROM `databases` WHERE `databases`.`db_server` = database_servers.id) as c_databases')
)->leftJoin('nodes', 'nodes.id', '=', 'database_servers.linked_node')
->paginate(20),
]); ]);
} }
public function getNew(Request $request) public function getNew(Request $request)
{ {
return view('admin.databases.new', [ return view('admin.databases.new', [
'nodes' => Models\Node::select('nodes.id', 'nodes.name', 'locations.long as a_location') 'nodes' => Models\Node::all()->load('location'),
->join('locations', 'locations.id', '=', 'nodes.location')
->get(),
]); ]);
} }
@ -78,15 +62,17 @@ class DatabaseController extends Controller
{ {
try { try {
$repo = new DatabaseRepository; $repo = new DatabaseRepository;
$repo->add($request->except([ $repo->add($request->only([
'_token', 'name',
'host',
'port',
'username',
'password',
'linked_node',
])); ]));
Alert::success('Successfully added a new database server to the system.')->flash(); Alert::success('Successfully added a new database server to the system.')->flash();
return redirect()->route('admin.databases', [ return redirect()->route('admin.databases', ['tab' => 'tab_dbservers']);
'tab' => 'tab_dbservers',
]);
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->route('admin.databases.new')->withErrors(json_decode($ex->getMessage()))->withInput(); return redirect()->route('admin.databases.new')->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) { } catch (\Exception $ex) {

View file

@ -24,7 +24,6 @@
namespace Pterodactyl\Http\Controllers\Admin; namespace Pterodactyl\Http\Controllers\Admin;
use DB;
use Alert; use Alert;
use Pterodactyl\Models; use Pterodactyl\Models;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -43,35 +42,21 @@ class LocationsController extends Controller
public function getIndex(Request $request) public function getIndex(Request $request)
{ {
return view('admin.locations.index', [ return view('admin.locations.index', [
'locations' => Models\Location::select( 'locations' => Models\Location::withCount('nodes', 'servers')->paginate(20),
'locations.*',
DB::raw('(SELECT COUNT(*) FROM nodes WHERE nodes.location = locations.id) as a_nodeCount'),
DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node IN (SELECT nodes.id FROM nodes WHERE nodes.location = locations.id)) as a_serverCount')
)->paginate(20),
]); ]);
} }
public function deleteLocation(Request $request, $id) public function deleteLocation(Request $request, $id)
{ {
$model = Models\Location::select( $location = Models\Location::withCount('nodes')->findOrFail($id);
'locations.id',
DB::raw('(SELECT COUNT(*) FROM nodes WHERE nodes.location = locations.id) as a_nodeCount'),
DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node IN (SELECT nodes.id FROM nodes WHERE nodes.location = locations.id)) as a_serverCount')
)->where('id', $id)->first();
if (! $model) { if ($location->nodes_count > 0) {
return response()->json([ return response()->json([
'error' => 'No location with that ID exists on the system.', 'error' => 'You cannot remove a location that is currently assigned to a node.',
], 404);
}
if ($model->a_nodeCount > 0 || $model->a_serverCount > 0) {
return response()->json([
'error' => 'You cannot remove a location that is currently assigned to a node or server.',
], 422); ], 422);
} }
$model->delete(); $location->delete();
return response('', 204); return response('', 204);
} }
@ -80,12 +65,12 @@ class LocationsController extends Controller
{ {
try { try {
$location = new LocationRepository; $location = new LocationRepository;
$location->edit($id, $request->all()); $location->edit($id, $request->only(['long', 'short']));
return response('', 204); return response('', 204);
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return response()->json([ return response()->json([
'error' => 'There was a validation error while processing this request. Location descriptions must be between 1 and 255 characters, and the location code must be between 1 and 10 characters with no spaces or special characters.', 'error' => 'There was a validation error while processing this request. Location descriptions must be between 1 and 255 characters, and the location code must be between 1 and 20 characters with no spaces or special characters.',
], 422); ], 422);
} catch (\Exception $ex) { } catch (\Exception $ex) {
// This gets caught and processed into JSON anyways. // This gets caught and processed into JSON anyways.
@ -97,9 +82,7 @@ class LocationsController extends Controller
{ {
try { try {
$location = new LocationRepository; $location = new LocationRepository;
$id = $location->create($request->except([ $location->create($request->only(['long', 'short']));
'_token',
]));
Alert::success('New location successfully added.')->flash(); Alert::success('New location successfully added.')->flash();
return redirect()->route('admin.locations'); return redirect()->route('admin.locations');

View file

@ -48,17 +48,15 @@ class NodesController extends Controller
public function getScript(Request $request, $id) public function getScript(Request $request, $id)
{ {
return response()->view('admin.nodes.remote.deploy', ['node' => Models\Node::findOrFail($id)])->header('Content-Type', 'text/plain'); return response()->view('admin.nodes.remote.deploy', [
'node' => Models\Node::findOrFail($id),
])->header('Content-Type', 'text/plain');
} }
public function getIndex(Request $request) public function getIndex(Request $request)
{ {
return view('admin.nodes.index', [ return view('admin.nodes.index', [
'nodes' => Models\Node::select( 'nodes' => Models\Node::with('location')->withCount('servers')->paginate(20),
'nodes.*',
'locations.long as a_locationName',
DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node = nodes.id) as a_serverCount')
)->join('locations', 'nodes.location', '=', 'locations.id')->paginate(20),
]); ]);
} }
@ -78,15 +76,18 @@ class NodesController extends Controller
public function postNew(Request $request) public function postNew(Request $request)
{ {
try { try {
$node = new NodeRepository; $repo = new NodeRepository;
$new = $node->create($request->except([ $node = $repo->create($request->only([
'_token', 'name', 'location_id', 'public',
'fqdn', 'scheme', 'memory',
'memory_overallocate', 'disk',
'disk_overallocate', 'daemonBase',
'daemonSFTP', 'daemonListen',
])); ]));
Alert::success('Successfully created new node. <strong>Before you can add any servers you need to first assign some IP addresses and ports.</strong>')->flash(); Alert::success('Successfully created new node that can be configured automatically on your remote machine by visiting the configuration tab. <strong>Before you can add any servers you need to first assign some IP addresses and ports.</strong>')->flash();
Alert::info('<strong>To simplify the node setup you can generate a token on the configuration tab.</strong>')->flash();
return redirect()->route('admin.nodes.view', [ return redirect()->route('admin.nodes.view', [
'id' => $new, 'id' => $node->id,
'tab' => 'tab_allocation', 'tab' => 'tab_allocation',
]); ]);
} catch (DisplayValidationException $e) { } catch (DisplayValidationException $e) {
@ -103,26 +104,16 @@ class NodesController extends Controller
public function getView(Request $request, $id) public function getView(Request $request, $id)
{ {
$node = Models\Node::findOrFail($id); $node = Models\Node::with(
'servers.user', 'servers.service',
'servers.allocations', 'location'
)->findOrFail($id);
$node->setRelation('allocations', $node->allocations()->with('server')->paginate(40));
return view('admin.nodes.view', [ return view('admin.nodes.view', [
'node' => $node, 'node' => $node,
'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail', 'services.name as a_serviceName') 'stats' => Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first(),
->join('users', 'users.id', '=', 'servers.owner')
->join('services', 'services.id', '=', 'servers.service')
->where('node', $id)->paginate(10, ['*'], 'servers'),
'stats' => Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(),
'locations' => Models\Location::all(), 'locations' => Models\Location::all(),
'allocations' => Models\Allocation::select('allocations.*', 'servers.name as assigned_to_name')
->where('allocations.node', $node->id)
->leftJoin('servers', 'servers.id', '=', 'allocations.assigned_to')
->orderBy('allocations.ip', 'asc')
->orderBy('allocations.port', 'asc')
->paginate(20, ['*'], 'allocations'),
'allocation_ips' => Models\Allocation::select('id', 'ip')
->where('node', $node->id)
->groupBy('ip')
->get(),
]); ]);
} }
@ -130,8 +121,12 @@ class NodesController extends Controller
{ {
try { try {
$node = new NodeRepository; $node = new NodeRepository;
$node->update($id, $request->except([ $node->update($id, $request->only([
'_token', 'name', 'location_id', 'public',
'fqdn', 'scheme', 'memory',
'memory_overallocate', 'disk',
'disk_overallocate', 'upload_size',
'daemonSFTP', 'daemonListen', 'reset_secret',
])); ]));
Alert::success('Successfully update this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash(); Alert::success('Successfully update this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash();
@ -156,7 +151,7 @@ class NodesController extends Controller
public function deallocateSingle(Request $request, $node, $allocation) public function deallocateSingle(Request $request, $node, $allocation)
{ {
$query = Models\Allocation::where('node', $node)->whereNull('assigned_to')->where('id', $allocation)->delete(); $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('id', $allocation)->delete();
if ((int) $query === 0) { if ((int) $query === 0) {
return response()->json([ return response()->json([
'error' => 'Unable to find an allocation matching those details to delete.', 'error' => 'Unable to find an allocation matching those details to delete.',
@ -168,7 +163,7 @@ class NodesController extends Controller
public function deallocateBlock(Request $request, $node) public function deallocateBlock(Request $request, $node)
{ {
$query = Models\Allocation::where('node', $node)->whereNull('assigned_to')->where('ip', $request->input('ip'))->delete(); $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('ip', $request->input('ip'))->delete();
if ((int) $query === 0) { if ((int) $query === 0) {
Alert::danger('There was an error while attempting to delete allocations on that IP.')->flash(); Alert::danger('There was an error while attempting to delete allocations on that IP.')->flash();
@ -204,7 +199,7 @@ class NodesController extends Controller
public function getAllocationsJson(Request $request, $id) public function getAllocationsJson(Request $request, $id)
{ {
$allocations = Models\Allocation::select('ip')->where('node', $id)->groupBy('ip')->get(); $allocations = Models\Allocation::select('ip')->where('node_id', $id)->groupBy('ip')->get();
return response()->json($allocations); return response()->json($allocations);
} }

View file

@ -24,7 +24,6 @@
namespace Pterodactyl\Http\Controllers\Admin; namespace Pterodactyl\Http\Controllers\Admin;
use DB;
use Log; use Log;
use Alert; use Alert;
use Storage; use Storage;
@ -42,64 +41,29 @@ class PackController extends Controller
// //
} }
protected function formatServices()
{
$options = Models\ServiceOptions::select(
'services.name AS p_service',
'service_options.id',
'service_options.name'
)->join('services', 'services.id', '=', 'service_options.parent_service')->get();
$array = [];
foreach ($options as &$option) {
if (! array_key_exists($option->p_service, $array)) {
$array[$option->p_service] = [];
}
$array[$option->p_service] = array_merge($array[$option->p_service], [[
'id' => $option->id,
'name' => $option->name,
]]);
}
return $array;
}
public function listAll(Request $request) public function listAll(Request $request)
{ {
return view('admin.services.packs.index', [ return view('admin.services.packs.index', ['services' => Models\Service::all()]);
'services' => Models\Service::all(),
]);
} }
public function listByOption(Request $request, $id) public function listByOption(Request $request, $id)
{ {
$option = Models\ServiceOptions::findOrFail($id);
return view('admin.services.packs.byoption', [ return view('admin.services.packs.byoption', [
'packs' => Models\ServicePack::where('option', $option->id)->get(), 'option' => Models\ServiceOption::with('service', 'packs')->findOrFail($id),
'service' => Models\Service::findOrFail($option->parent_service),
'option' => $option,
]); ]);
} }
public function listByService(Request $request, $id) public function listByService(Request $request, $id)
{ {
return view('admin.services.packs.byservice', [ return view('admin.services.packs.byservice', [
'service' => Models\Service::findOrFail($id), 'service' => Models\Service::with('options', 'options.packs')->findOrFail($id),
'options' => Models\ServiceOptions::select(
'service_options.id',
'service_options.name',
DB::raw('(SELECT COUNT(id) FROM service_packs WHERE service_packs.option = service_options.id) AS p_count')
)->where('parent_service', $id)->get(),
]); ]);
} }
public function new(Request $request, $opt = null) public function new(Request $request, $opt = null)
{ {
return view('admin.services.packs.new', [ return view('admin.services.packs.new', [
'services' => $this->formatServices(), 'services' => Models\Service::with('options')->get(),
'packFor' => $opt,
]); ]);
} }
@ -107,12 +71,14 @@ class PackController extends Controller
{ {
try { try {
$repo = new Pack; $repo = new Pack;
$id = $repo->create($request->except([ $pack = $repo->create($request->only([
'_token', 'name', 'version', 'description',
'option', 'selectable', 'visible',
'file_upload',
])); ]));
Alert::success('Successfully created new service!')->flash(); Alert::success('Successfully created new service!')->flash();
return redirect()->route('admin.services.packs.edit', $id)->withInput(); return redirect()->route('admin.services.packs.edit', $pack->id)->withInput();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.packs.new', $request->input('option'))->withErrors(json_decode($ex->getMessage()))->withInput(); return redirect()->route('admin.services.packs.new', $request->input('option'))->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) { } catch (DisplayException $ex) {
@ -127,15 +93,12 @@ class PackController extends Controller
public function edit(Request $request, $id) public function edit(Request $request, $id)
{ {
$pack = Models\ServicePack::findOrFail($id); $pack = Models\ServicePack::with('option.service')->findOrFail($id);
$option = Models\ServiceOptions::select('id', 'parent_service', 'name')->where('id', $pack->option)->first();
return view('admin.services.packs.edit', [ return view('admin.services.packs.edit', [
'pack' => $pack, 'pack' => $pack,
'services' => $this->formatServices(), 'services' => Models\Service::all()->load('options'),
'files' => Storage::files('packs/' . $pack->uuid), 'files' => Storage::files('packs/' . $pack->uuid),
'service' => Models\Service::findOrFail($option->parent_service),
'option' => $option,
]); ]);
} }
@ -159,8 +122,9 @@ class PackController extends Controller
} else { } else {
try { try {
$repo = new Pack; $repo = new Pack;
$repo->update($id, $request->except([ $repo->update($id, $request->only([
'_token', 'name', 'version', 'description',
'option', 'selectable', 'visible',
])); ]));
Alert::success('Service pack has been successfully updated.')->flash(); Alert::success('Service pack has been successfully updated.')->flash();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
@ -183,14 +147,6 @@ class PackController extends Controller
'description' => $pack->dscription, 'description' => $pack->dscription,
'selectable' => (bool) $pack->selectable, 'selectable' => (bool) $pack->selectable,
'visible' => (bool) $pack->visible, 'visible' => (bool) $pack->visible,
'build' => [
'memory' => $pack->build_memory,
'swap' => $pack->build_swap,
'cpu' => $pack->build_cpu,
'io' => $pack->build_io,
'container' => $pack->build_container,
'script' => $pack->build_script,
],
]; ];
$filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_');
@ -223,8 +179,7 @@ class PackController extends Controller
public function uploadForm(Request $request, $for = null) public function uploadForm(Request $request, $for = null)
{ {
return view('admin.services.packs.upload', [ return view('admin.services.packs.upload', [
'services' => $this->formatServices(), 'services' => Models\Service::all()->load('options'),
'for' => $for,
]); ]);
} }
@ -232,12 +187,10 @@ class PackController extends Controller
{ {
try { try {
$repo = new Pack; $repo = new Pack;
$id = $repo->createWithTemplate($request->except([ $pack = $repo->createWithTemplate($request->only(['option', 'file_upload']));
'_token',
]));
Alert::success('Successfully created new service!')->flash(); Alert::success('Successfully created new service!')->flash();
return redirect()->route('admin.services.packs.edit', $id)->withInput(); return redirect()->route('admin.services.packs.edit', $pack->id)->withInput();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->back()->withErrors(json_decode($ex->getMessage()))->withInput(); return redirect()->back()->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) { } catch (DisplayException $ex) {

View file

@ -24,7 +24,6 @@
namespace Pterodactyl\Http\Controllers\Admin; namespace Pterodactyl\Http\Controllers\Admin;
use DB;
use Log; use Log;
use Alert; use Alert;
use Pterodactyl\Models; use Pterodactyl\Models;
@ -47,63 +46,8 @@ class ServersController extends Controller
public function getIndex(Request $request) public function getIndex(Request $request)
{ {
$query = Models\Server::withTrashed()->select(
'servers.*',
'nodes.name as a_nodeName',
'users.email as a_ownerEmail',
'allocations.ip',
'allocations.port',
'allocations.ip_alias'
)->join('nodes', 'servers.node', '=', 'nodes.id')
->join('users', 'servers.owner', '=', 'users.id')
->join('allocations', 'servers.allocation', '=', 'allocations.id');
if ($request->input('filter') && ! is_null($request->input('filter'))) {
preg_match_all('/[^\s"\']+|"([^"]*)"|\'([^\']*)\'/', urldecode($request->input('filter')), $matches);
foreach ($matches[0] as $match) {
$match = str_replace('"', '', $match);
if (strpos($match, ':')) {
list($field, $term) = explode(':', $match);
if ($field === 'node') {
$field = 'nodes.name';
} elseif ($field === 'owner') {
$field = 'users.email';
} elseif (! strpos($field, '.')) {
$field = 'servers.' . $field;
}
$query->orWhere($field, 'LIKE', '%' . $term . '%');
} else {
$query->where('servers.name', 'LIKE', '%' . $match . '%');
$query->orWhere([
['servers.username', 'LIKE', '%' . $match . '%'],
['users.email', 'LIKE', '%' . $match . '%'],
['allocations.port', 'LIKE', '%' . $match . '%'],
['allocations.ip', 'LIKE', '%' . $match . '%'],
]);
}
}
}
try {
$servers = $query->paginate(20);
} catch (\Exception $ex) {
Alert::warning('There was an error with the search parameters provided.');
$servers = Models\Server::withTrashed()->select(
'servers.*',
'nodes.name as a_nodeName',
'users.email as a_ownerEmail',
'allocations.ip',
'allocations.port',
'allocations.ip_alias'
)->join('nodes', 'servers.node', '=', 'nodes.id')
->join('users', 'servers.owner', '=', 'users.id')
->join('allocations', 'servers.allocation', '=', 'allocations.id')
->paginate(20);
}
return view('admin.servers.index', [ return view('admin.servers.index', [
'servers' => $servers, 'servers' => Models\Server::withTrashed()->with('node', 'user')->paginate(25),
]); ]);
} }
@ -117,47 +61,21 @@ class ServersController extends Controller
public function getView(Request $request, $id) public function getView(Request $request, $id)
{ {
$server = Models\Server::withTrashed()->select( $server = Models\Server::withTrashed()->with(
'servers.*', 'user', 'option.variables', 'variables',
'users.email as a_ownerEmail', 'node.allocations', 'databases.host'
'services.name as a_serviceName', )->findOrFail($id);
DB::raw('IFNULL(service_options.executable, services.executable) as a_serviceExecutable'),
'service_options.docker_image',
'service_options.name as a_servceOptionName',
'allocations.ip',
'allocations.port',
'allocations.ip_alias'
)->join('nodes', 'servers.node', '=', 'nodes.id')
->join('users', 'servers.owner', '=', 'users.id')
->join('services', 'servers.service', '=', 'services.id')
->join('service_options', 'servers.option', '=', 'service_options.id')
->join('allocations', 'servers.allocation', '=', 'allocations.id')
->where('servers.id', $id)
->first();
if (! $server) { $server->option->variables->transform(function ($item, $key) use ($server) {
return abort(404); $item->server_value = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first();
}
return $item;
});
return view('admin.servers.view', [ return view('admin.servers.view', [
'server' => $server, 'server' => $server,
'node' => Models\Node::select( 'assigned' => $server->node->allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'),
'nodes.*', 'unassigned' => $server->node->allocations->where('server_id', null)->sortBy('port')->sortBy('ip'),
'locations.long as a_locationName'
)->join('locations', 'nodes.location', '=', 'locations.id')
->where('nodes.id', $server->node)
->first(),
'assigned' => Models\Allocation::where('assigned_to', $id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(),
'unassigned' => Models\Allocation::where('node', $server->node)->whereNull('assigned_to')->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(),
'startup' => Models\ServiceVariables::select('service_variables.*', 'server_variables.variable_value as a_serverValue')
->join('server_variables', 'server_variables.variable_id', '=', 'service_variables.id')
->where('service_variables.option_id', $server->option)
->where('server_variables.server_id', $server->id)
->get(),
'databases' => Models\Database::select('databases.*', 'database_servers.host as a_host', 'database_servers.port as a_port')
->where('server_id', $server->id)
->join('database_servers', 'database_servers.id', '=', 'databases.db_server')
->get(),
'db_servers' => Models\DatabaseServer::all(), 'db_servers' => Models\DatabaseServer::all(),
]); ]);
} }
@ -166,9 +84,9 @@ class ServersController extends Controller
{ {
try { try {
$server = new ServerRepository; $server = new ServerRepository;
$response = $server->create($request->all()); $response = $server->create($request->except('_token'));
return redirect()->route('admin.servers.view', ['id' => $response]); return redirect()->route('admin.servers.view', ['id' => $response->id]);
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput(); return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) { } catch (DisplayException $ex) {
@ -197,7 +115,7 @@ class ServersController extends Controller
], 500); ], 500);
} }
return response()->json(Models\Node::select('id', 'name', 'public')->where('location', $request->input('location'))->get()); return response()->json(Models\Node::select('id', 'name', 'public')->where('location_id', $request->input('location'))->get());
} }
/** /**
@ -214,7 +132,7 @@ class ServersController extends Controller
], 500); ], 500);
} }
$ips = Models\Allocation::where('node', $request->input('node'))->whereNull('assigned_to')->get(); $ips = Models\Allocation::where('node_id', $request->input('node'))->whereNull('server_id')->get();
$listing = []; $listing = [];
foreach ($ips as &$ip) { foreach ($ips as &$ip) {
@ -234,7 +152,7 @@ class ServersController extends Controller
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function postNewServerServiceOptions(Request $request) public function postNewServerServiceOption(Request $request)
{ {
if (! $request->input('service')) { if (! $request->input('service')) {
return response()->json([ return response()->json([
@ -244,7 +162,7 @@ class ServersController extends Controller
$service = Models\Service::select('executable', 'startup')->where('id', $request->input('service'))->first(); $service = Models\Service::select('executable', 'startup')->where('id', $request->input('service'))->first();
return response()->json(Models\ServiceOptions::select('id', 'name', 'docker_image')->where('parent_service', $request->input('service'))->orderBy('name', 'asc')->get()); return response()->json(Models\ServiceOption::select('id', 'name', 'docker_image')->where('service_id', $request->input('service'))->orderBy('name', 'asc')->get());
} }
/** /**
@ -261,18 +179,15 @@ class ServersController extends Controller
], 500); ], 500);
} }
$option = Models\ServiceOptions::select( $option = Models\ServiceOption::with('variables')->with(['packs' => function ($query) {
DB::raw('COALESCE(service_options.executable, services.executable) as executable'), $query->where('selectable', true);
DB::raw('COALESCE(service_options.startup, services.startup) as startup') }])->findOrFail($request->input('option'));
)->leftJoin('services', 'services.id', '=', 'service_options.parent_service')
->where('service_options.id', $request->input('option'))
->first();
return response()->json([ return response()->json([
'packs' => Models\ServicePack::select('id', 'name', 'version')->where('option', $request->input('option'))->where('selectable', true)->get(), 'packs' => $option->packs,
'variables' => Models\ServiceVariables::where('option_id', $request->input('option'))->get(), 'variables' => $option->variables,
'exec' => $option->executable, 'exec' => $option->display_executable,
'startup' => $option->startup, 'startup' => $option->display_startup,
]); ]);
} }
@ -309,9 +224,7 @@ class ServersController extends Controller
{ {
try { try {
$server = new ServerRepository; $server = new ServerRepository;
$server->updateContainer($id, [ $server->updateContainer($id, ['image' => $request->input('docker_image')]);
'image' => $request->input('docker_image'),
]);
Alert::success('Successfully updated this server\'s docker image.')->flash(); Alert::success('Successfully updated this server\'s docker image.')->flash();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->route('admin.servers.view', [ return redirect()->route('admin.servers.view', [
@ -333,17 +246,13 @@ class ServersController extends Controller
public function postUpdateServerToggleBuild(Request $request, $id) public function postUpdateServerToggleBuild(Request $request, $id)
{ {
$server = Models\Server::findOrFail($id); $server = Models\Server::with('node')->findOrFail($id);
$node = Models\Node::findOrFail($server->node);
$client = Models\Node::guzzleRequest($server->node);
try { try {
$res = $client->request('POST', '/server/rebuild', [ $res = $server->node->guzzleClient([
'headers' => [
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
'X-Access-Token' => $node->daemonSecret, 'X-Access-Token' => $server->node->daemonSecret,
], ])->request('POST', '/server/rebuild');
]);
Alert::success('A rebuild has been queued successfully. It will run the next time this server is booted.')->flash(); Alert::success('A rebuild has been queued successfully. It will run the next time this server is booted.')->flash();
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (\GuzzleHttp\Exception\TransferException $ex) {
Log::warning($ex); Log::warning($ex);
@ -360,15 +269,11 @@ class ServersController extends Controller
{ {
try { try {
$server = new ServerRepository; $server = new ServerRepository;
$server->changeBuild($id, [ $server->changeBuild($id, $request->only([
'default' => $request->input('default'), 'default', 'add_additional',
'add_additional' => $request->input('add_additional'), 'remove_additional', 'memory',
'remove_additional' => $request->input('remove_additional'), 'swap', 'io', 'cpu',
'memory' => $request->input('memory'), ]));
'swap' => $request->input('swap'),
'io' => $request->input('io'),
'cpu' => $request->input('cpu'),
]);
Alert::success('Server details were successfully updated.')->flash(); Alert::success('Server details were successfully updated.')->flash();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->route('admin.servers.view', [ return redirect()->route('admin.servers.view', [
@ -458,8 +363,8 @@ class ServersController extends Controller
{ {
try { try {
$repo = new DatabaseRepository; $repo = new DatabaseRepository;
$repo->create($id, $request->except([ $repo->create($id, $request->only([
'_token', 'db_server', 'database', 'remote',
])); ]));
Alert::success('Added new database to this server.')->flash(); Alert::success('Added new database to this server.')->flash();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {

View file

@ -24,7 +24,6 @@
namespace Pterodactyl\Http\Controllers\Admin; namespace Pterodactyl\Http\Controllers\Admin;
use DB;
use Log; use Log;
use Alert; use Alert;
use Storage; use Storage;
@ -45,10 +44,7 @@ class ServiceController extends Controller
public function getIndex(Request $request) public function getIndex(Request $request)
{ {
return view('admin.services.index', [ return view('admin.services.index', [
'services' => Models\Service::select( 'services' => Models\Service::withCount('servers')->get(),
'services.*',
DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.service = services.id) as c_servers')
)->get(),
]); ]);
} }
@ -61,12 +57,13 @@ class ServiceController extends Controller
{ {
try { try {
$repo = new ServiceRepository\Service; $repo = new ServiceRepository\Service;
$id = $repo->create($request->except([ $service = $repo->create($request->only([
'_token', 'name', 'description', 'file',
'executable', 'startup',
])); ]));
Alert::success('Successfully created new service!')->flash(); Alert::success('Successfully created new service!')->flash();
return redirect()->route('admin.services.service', $id); return redirect()->route('admin.services.service', $service->id);
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.new')->withErrors(json_decode($ex->getMessage()))->withInput(); return redirect()->route('admin.services.new')->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) { } catch (DisplayException $ex) {
@ -82,11 +79,7 @@ class ServiceController extends Controller
public function getService(Request $request, $service) public function getService(Request $request, $service)
{ {
return view('admin.services.view', [ return view('admin.services.view', [
'service' => Models\Service::findOrFail($service), 'service' => Models\Service::with('options', 'options.servers')->findOrFail($service),
'options' => Models\ServiceOptions::select(
'service_options.*',
DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.option = service_options.id) as c_servers')
)->where('parent_service', $service)->get(),
]); ]);
} }
@ -94,8 +87,9 @@ class ServiceController extends Controller
{ {
try { try {
$repo = new ServiceRepository\Service; $repo = new ServiceRepository\Service;
$repo->update($service, $request->except([ $repo->update($service, $request->only([
'_token', 'name', 'description', 'file',
'executable', 'startup',
])); ]));
Alert::success('Successfully updated this service.')->flash(); Alert::success('Successfully updated this service.')->flash();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
@ -130,25 +124,19 @@ class ServiceController extends Controller
public function getOption(Request $request, $service, $option) public function getOption(Request $request, $service, $option)
{ {
$opt = Models\ServiceOptions::findOrFail($option); $option = Models\ServiceOption::with('service', 'variables')->findOrFail($option);
$option->setRelation('servers', $option->servers()->with('user')->paginate(25));
return view('admin.services.options.view', [ return view('admin.services.options.view', ['option' => $option]);
'service' => Models\Service::findOrFail($opt->parent_service),
'option' => $opt,
'variables' => Models\ServiceVariables::where('option_id', $option)->get(),
'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail')
->join('users', 'users.id', '=', 'servers.owner')
->where('option', $option)
->paginate(10),
]);
} }
public function postOption(Request $request, $service, $option) public function postOption(Request $request, $service, $option)
{ {
try { try {
$repo = new ServiceRepository\Option; $repo = new ServiceRepository\Option;
$repo->update($option, $request->except([ $repo->update($option, $request->only([
'_token', 'name', 'description', 'tag',
'executable', 'docker_image', 'startup',
])); ]));
Alert::success('Option settings successfully updated.')->flash(); Alert::success('Option settings successfully updated.')->flash();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
@ -164,13 +152,12 @@ class ServiceController extends Controller
public function deleteOption(Request $request, $service, $option) public function deleteOption(Request $request, $service, $option)
{ {
try { try {
$service = Models\ServiceOptions::select('parent_service')->where('id', $option)->first();
$repo = new ServiceRepository\Option; $repo = new ServiceRepository\Option;
$repo->delete($option); $repo->delete($option);
Alert::success('Successfully deleted that option.')->flash(); Alert::success('Successfully deleted that option.')->flash();
return redirect()->route('admin.services.service', $service->parent_service); return redirect()->route('admin.services.service', $service);
} catch (DisplayException $ex) { } catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash(); Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) { } catch (\Exception $ex) {
@ -218,8 +205,7 @@ class ServiceController extends Controller
public function getNewVariable(Request $request, $service, $option) public function getNewVariable(Request $request, $service, $option)
{ {
return view('admin.services.options.variable', [ return view('admin.services.options.variable', [
'service' => Models\Service::findOrFail($service), 'option' => Models\ServiceOption::with('service')->findOrFail($option),
'option' => Models\ServiceOptions::where('parent_service', $service)->where('id', $option)->firstOrFail(),
]); ]);
} }
@ -227,8 +213,10 @@ class ServiceController extends Controller
{ {
try { try {
$repo = new ServiceRepository\Variable; $repo = new ServiceRepository\Variable;
$repo->create($option, $request->except([ $repo->create($option, $request->only([
'_token', 'name', 'description', 'env_variable',
'default_value', 'user_viewable',
'user_editable', 'required', 'regex',
])); ]));
Alert::success('Successfully added new variable to this option.')->flash(); Alert::success('Successfully added new variable to this option.')->flash();
@ -305,9 +293,7 @@ class ServiceController extends Controller
{ {
try { try {
$repo = new ServiceRepository\Service; $repo = new ServiceRepository\Service;
$repo->updateFile($serviceId, $request->except([ $repo->updateFile($serviceId, $request->only(['file', 'contents']));
'_token',
]));
return response('', 204); return response('', 204);
} catch (DisplayException $ex) { } catch (DisplayException $ex) {

View file

@ -29,7 +29,6 @@ use Log;
use Alert; use Alert;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\UserRepository; use Pterodactyl\Repositories\UserRepository;
@ -45,35 +44,11 @@ class UserController extends Controller
// //
} }
// @TODO: implement nicolaslopezj/searchable to clean up this disaster.
public function getIndex(Request $request) public function getIndex(Request $request)
{ {
$query = User::select('users.*');
if ($request->input('filter') && ! is_null($request->input('filter'))) {
preg_match_all('/[^\s"\']+|"([^"]*)"|\'([^\']*)\'/', urldecode($request->input('filter')), $matches);
foreach ($matches[0] as $match) {
$match = str_replace('"', '', $match);
if (strpos($match, ':')) {
list($field, $term) = explode(':', $match);
$query->orWhere($field, 'LIKE', '%' . $term . '%');
} else {
$query->where('email', 'LIKE', '%' . $match . '%');
$query->orWhere([
['uuid', 'LIKE', '%' . $match . '%'],
['root_admin', 'LIKE', '%' . $match . '%'],
]);
}
}
}
try {
$users = $query->paginate(20);
} catch (\Exception $ex) {
Alert::warning('There was an error with the search parameters provided.');
$users = User::all()->paginate(20);
}
return view('admin.users.index', [ return view('admin.users.index', [
'users' => $users, 'users' => User::paginate(25),
]); ]);
} }
@ -85,12 +60,7 @@ class UserController extends Controller
public function getView(Request $request, $id) public function getView(Request $request, $id)
{ {
return view('admin.users.view', [ return view('admin.users.view', [
'user' => User::findOrFail($id), 'user' => User::with('servers.node')->findOrFail($id),
'servers' => Server::select('servers.*', 'nodes.name as nodeName', 'locations.long as location')
->join('nodes', 'servers.node', '=', 'nodes.id')
->join('locations', 'nodes.location', '=', 'locations.id')
->where('owner', $id)
->get(),
]); ]);
} }
@ -117,12 +87,8 @@ class UserController extends Controller
try { try {
$user = new UserRepository; $user = new UserRepository;
$userid = $user->create($request->only([ $userid = $user->create($request->only([
'email', 'email', 'password', 'name_first',
'password', 'name_last', 'username', 'root_admin',
'name_first',
'name_last',
'username',
'root_admin',
])); ]));
Alert::success('Account has been successfully created.')->flash(); Alert::success('Account has been successfully created.')->flash();
@ -142,12 +108,8 @@ class UserController extends Controller
try { try {
$repo = new UserRepository; $repo = new UserRepository;
$repo->update($user, $request->only([ $repo->update($user, $request->only([
'email', 'email', 'password', 'name_first',
'password', 'name_last', 'username', 'root_admin',
'name_first',
'name_last',
'username',
'root_admin',
])); ]));
Alert::success('User account was successfully updated.')->flash(); Alert::success('User account was successfully updated.')->flash();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
@ -162,10 +124,6 @@ class UserController extends Controller
public function getJson(Request $request) public function getJson(Request $request)
{ {
foreach (User::select('email')->get() as $user) { return User::select('email')->get()->pluck('email');
$resp[] = $user->email;
}
return $resp;
} }
} }

View file

@ -27,6 +27,7 @@ namespace Pterodactyl\Http\Controllers\Auth;
use Auth; use Auth;
use Alert; use Alert;
use Cache;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use PragmaRX\Google2FA\Google2FA; use PragmaRX\Google2FA\Google2FA;
@ -86,8 +87,11 @@ class LoginController extends Controller
*/ */
public function login(Request $request) public function login(Request $request)
{ {
// Check wether the user identifier is an email address or a username
$isEmail = str_contains($request->input('user'), '@');
$this->validate($request, [ $this->validate($request, [
'email' => 'required|email', 'user' => $isEmail ? 'required|email' : 'required|string',
'password' => 'required', 'password' => 'required',
]); ]);
@ -97,9 +101,9 @@ class LoginController extends Controller
return $this->sendLockoutResponse($request); return $this->sendLockoutResponse($request);
} }
// Is the email & password valid? // Is the user (email or username) & password valid?
if (! Auth::once([ if (! Auth::once([
'email' => $request->input('email'), $isEmail ? 'email' : 'username' => $request->input('user'),
'password' => $request->input('password'), 'password' => $request->input('password'),
], $request->has('remember'))) { ], $request->has('remember'))) {
if (! $lockedOut) { if (! $lockedOut) {
@ -110,33 +114,60 @@ class LoginController extends Controller
} }
// Verify TOTP Token was Valid // Verify TOTP Token was Valid
if (Auth::user()->use_totp === 1) { if (Auth::user()->use_totp) {
$G2FA = new Google2FA(); $verifyKey = str_random(64);
if (is_null($request->input('totp_token')) || ! $G2FA->verifyKey(Auth::user()->totp_secret, $request->input('totp_token'))) { Cache::put($verifyKey, Auth::user()->id, 5);
if (! $lockedOut) {
$this->incrementLoginAttempts($request);
}
Alert::danger(trans('auth.totp_failed'))->flash(); return redirect()->route('auth.totp')->with('authentication_token', $verifyKey);
} else {
return $this->sendFailedLoginResponse($request);
}
}
// Successfully Authenticated.
Auth::login(Auth::user(), $request->has('remember')); Auth::login(Auth::user(), $request->has('remember'));
return $this->sendLoginResponse($request); return $this->sendLoginResponse($request);
} }
}
/** public function totp(Request $request)
* Check if the provided user has TOTP enabled.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function checkTotp(Request $request)
{ {
return response()->json(User::select('id')->where('email', $request->input('email'))->where('use_totp', 1)->first()); $verifyKey = $request->session()->get('authentication_token');
if (is_null($verifyKey) || Auth::user()) {
return redirect()->route('auth.login');
}
return view('auth.totp', [
'verify_key' => $verifyKey,
'remember' => $request->has('remember'),
]);
}
public function totpCheckpoint(Request $request)
{
$G2FA = new Google2FA();
if (is_null($request->input('verify_token'))) {
$this->incrementLoginAttempts($request);
Alert::danger(trans('auth.totp_failed'))->flash();
return redirect()->route('auth.login');
}
$user = User::where('id', Cache::pull($request->input('verify_token')))->first();
if (! $user) {
$this->incrementLoginAttempts($request);
Alert::danger(trans('auth.totp_failed'))->flash();
return redirect()->route('auth.login');
}
if (! is_null($request->input('2fa_token')) && $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'), 1)) {
Auth::login($user, $request->has('remember'));
return redirect()->intended($this->redirectPath());
} else {
$this->incrementLoginAttempts($request);
Alert::danger(trans('auth.2fa_failed'))->flash();
return redirect()->route('auth.login');
}
} }
} }

View file

@ -38,13 +38,8 @@ class APIController extends Controller
{ {
public function index(Request $request) public function index(Request $request)
{ {
$keys = Models\APIKey::where('user', $request->user()->id)->get();
foreach ($keys as &$key) {
$key->permissions = Models\APIPermission::where('key_id', $key->id)->get();
}
return view('base.api.index', [ return view('base.api.index', [
'keys' => $keys, 'keys' => Models\APIKey::where('user_id', $request->user()->id)->get(),
]); ]);
} }
@ -57,8 +52,11 @@ class APIController extends Controller
{ {
try { try {
$repo = new APIRepository($request->user()); $repo = new APIRepository($request->user());
$secret = $repo->create($request->except(['_token'])); $secret = $repo->create($request->only([
Alert::success('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.<br /><br /><code>' . $secret . '</code>')->flash(); 'memo', 'allowed_ips',
'adminPermissions', 'permissions',
]));
Alert::success('An API Key-Pair has successfully been generated. The API secret for this public key is shown below and will not be shown again.<br /><br /><code>' . $secret . '</code>')->flash();
return redirect()->route('account.api'); return redirect()->route('account.api');
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
@ -81,6 +79,8 @@ class APIController extends Controller
return response('', 204); return response('', 204);
} catch (\Exception $ex) { } catch (\Exception $ex) {
Log::error($ex);
return response()->json([ return response()->json([
'error' => 'An error occured while attempting to remove this key.', 'error' => 'An error occured while attempting to remove this key.',
], 503); ], 503);

View file

@ -26,7 +26,6 @@
namespace Pterodactyl\Http\Controllers\Base; namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
class IndexController extends Controller class IndexController extends Controller
@ -48,7 +47,7 @@ class IndexController extends Controller
public function getIndex(Request $request) public function getIndex(Request $request)
{ {
return view('base.index', [ return view('base.index', [
'servers' => Server::getUserServers(10), 'servers' => $request->user()->serverAccessCollection(10),
]); ]);
} }

View file

@ -118,8 +118,7 @@ class SecurityController extends Controller
public function revoke(Request $request, $id) public function revoke(Request $request, $id)
{ {
$session = Session::where('id', $id)->where('user_id', $request->user()->id)->firstOrFail(); Session::where('user_id', $request->user()->id)->findOrFail($id)->delete();
$session->delete();
return redirect()->route('account.security'); return redirect()->route('account.security');
} }

View file

@ -0,0 +1,95 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Http\Controllers\Daemon;
use Storage;
use Pterodactyl\Models;
use Illuminate\Http\Request;
use Pterodactyl\Http\Controllers\Controller;
class PackController extends Controller
{
/**
* Controller Constructor.
*/
public function __construct()
{
//
}
/**
* Pulls an install pack archive from the system.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function pull(Request $request, $uuid)
{
$pack = Models\ServicePack::where('uuid', $uuid)->first();
if (! $pack) {
return response()->json(['error' => 'No such pack.'], 404);
}
if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) {
return response()->json(['error' => 'There is no archive available for this pack.'], 503);
}
return response()->download(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz'));
}
/**
* Returns the hash information for a pack.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function hash(Request $request, $uuid)
{
$pack = Models\ServicePack::where('uuid', $uuid)->first();
if (! $pack) {
return response()->json(['error' => 'No such pack.'], 404);
}
if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) {
return response()->json(['error' => 'There is no archive available for this pack.'], 503);
}
return response()->json([
'archive.tar.gz' => sha1_file(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz')),
]);
}
/**
* Pulls an update pack archive from the system.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function pullUpdate(Request $request)
{
}
}

View file

@ -28,7 +28,6 @@ use Carbon\Carbon;
use Pterodactyl\Models; use Pterodactyl\Models;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\NotificationService;
class RemoteController extends Controller class RemoteController extends Controller
{ {
@ -42,7 +41,7 @@ class RemoteController extends Controller
public function postDownload(Request $request) public function postDownload(Request $request)
{ {
$download = Models\Download::where('token', $request->input('token', '00'))->first(); $download = Models\Download::where('token', $request->input('token'))->first();
if (! $download) { if (! $download) {
return response()->json([ return response()->json([
'error' => 'An invalid request token was recieved with this request.', 'error' => 'An invalid request token was recieved with this request.',
@ -59,18 +58,17 @@ class RemoteController extends Controller
public function postInstall(Request $request) public function postInstall(Request $request)
{ {
$server = Models\Server::where('uuid', $request->input('server'))->first(); $server = Models\Server::where('uuid', $request->input('server'))->with('node')->first();
if (! $server) { if (! $server) {
return response()->json([ return response()->json([
'error' => 'No server by that ID was found on the system.', 'error' => 'No server by that ID was found on the system.',
], 422); ], 422);
} }
$node = Models\Node::findOrFail($server->node);
$hmac = $request->input('signed'); $hmac = $request->input('signed');
$status = $request->input('installed'); $status = $request->input('installed');
if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $node->daemonSecret, true)) { if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $server->node->daemonSecret, true)) {
return response()->json([ return response()->json([
'error' => 'Signed HMAC was invalid.', 'error' => 'Signed HMAC was invalid.',
], 403); ], 403);
@ -86,17 +84,15 @@ class RemoteController extends Controller
public function event(Request $request) public function event(Request $request)
{ {
$server = Models\Server::where('uuid', $request->input('server'))->first(); $server = Models\Server::where('uuid', $request->input('server'))->with('node')->first();
if (! $server) { if (! $server) {
return response()->json([ return response()->json([
'error' => 'No server by that ID was found on the system.', 'error' => 'No server by that ID was found on the system.',
], 422); ], 422);
} }
$node = Models\Node::findOrFail($server->node);
$hmac = $request->input('signed'); $hmac = $request->input('signed');
if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $node->daemonSecret, true)) { if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $server->node->daemonSecret, true)) {
return response()->json([ return response()->json([
'error' => 'Signed HMAC was invalid.', 'error' => 'Signed HMAC was invalid.',
], 403); ], 403);
@ -130,7 +126,6 @@ class RemoteController extends Controller
$token->delete(); $token->delete();
// Manually as getConfigurationAsJson() returns it in correct format already // Manually as getConfigurationAsJson() returns it in correct format already
return response($node->getConfigurationAsJson(), 200) return response($node->getConfigurationAsJson())->header('Content-Type', 'text/json');
->header('Content-Type', 'application/json');
} }
} }

View file

@ -67,18 +67,22 @@ class AjaxController extends Controller
*/ */
public function getStatus(Request $request, $uuid) public function getStatus(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
if (! $server) { if (! $server) {
return response()->json([], 404); return response()->json([], 404);
} }
$client = Models\Node::guzzleRequest($server->node); if (! $server->installed) {
return response()->json(['status' => 20]);
}
if ($server->suspended) {
return response()->json(['status' => 30]);
}
try { try {
$res = $client->request('GET', '/server', [ $res = $server->guzzleClient()->request('GET', '/server');
'headers' => Models\Server::getGuzzleHeaders($uuid),
]);
if ($res->getStatusCode() === 200) { if ($res->getStatusCode() === 200) {
return response()->json(json_decode($res->getBody())); return response()->json(json_decode($res->getBody()));
} }
@ -98,10 +102,10 @@ class AjaxController extends Controller
*/ */
public function postDirectoryList(Request $request, $uuid) public function postDirectoryList(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/');
$this->authorize('list-files', $server); $this->authorize('list-files', $server);
$this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/');
$prevDir = [ $prevDir = [
'header' => ($this->directory !== '/') ? $this->directory : '', 'header' => ($this->directory !== '/') ? $this->directory : '',
]; ];
@ -149,7 +153,7 @@ class AjaxController extends Controller
*/ */
public function postSaveFile(Request $request, $uuid) public function postSaveFile(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('save-files', $server); $this->authorize('save-files', $server);
$controller = new Repositories\Daemon\FileRepository($uuid); $controller = new Repositories\Daemon\FileRepository($uuid);
@ -175,17 +179,17 @@ class AjaxController extends Controller
*/ */
public function postSetPrimary(Request $request, $uuid) public function postSetPrimary(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid)->load('allocations');
$this->authorize('set-connection', $server); $this->authorize('set-connection', $server);
if ((int) $request->input('allocation') === $server->allocation) { if ((int) $request->input('allocation') === $server->allocation_id) {
return response()->json([ return response()->json([
'error' => 'You are already using this as your default connection.', 'error' => 'You are already using this as your default connection.',
], 409); ], 409);
} }
try { try {
$allocation = Models\Allocation::where('id', $request->input('allocation'))->where('assigned_to', $server->id)->first(); $allocation = $server->allocations->where('id', $request->input('allocation'))->where('server_id', $server->id)->first();
if (! $allocation) { if (! $allocation) {
return response()->json([ return response()->json([
'error' => 'No allocation matching your request was found in the system.', 'error' => 'No allocation matching your request was found in the system.',
@ -217,10 +221,10 @@ class AjaxController extends Controller
public function postResetDatabasePassword(Request $request, $uuid) public function postResetDatabasePassword(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$database = Models\Database::where('id', $request->input('database'))->where('server_id', $server->id)->firstOrFail();
$this->authorize('reset-db-password', $server); $this->authorize('reset-db-password', $server);
$database = Models\Database::where('id', $request->input('database'))->where('server_id', $server->id)->firstOrFail();
try { try {
$repo = new Repositories\DatabaseRepository; $repo = new Repositories\DatabaseRepository;
$password = str_random(16); $password = str_random(16);

View file

@ -28,7 +28,6 @@ use DB;
use Log; use Log;
use Uuid; use Uuid;
use Alert; use Alert;
use Javascript;
use Pterodactyl\Models; use Pterodactyl\Models;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
@ -55,14 +54,11 @@ class ServerController extends Controller
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View * @return \Illuminate\Contracts\View\View
*/ */
public function getIndex(Request $request) public function getIndex(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($request->route()->server); $server = Models\Server::byUuid($uuid);
$node = Models\Node::find($server->node);
Javascript::put([ $server->js([
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
'meta' => [ 'meta' => [
'saveFile' => route('server.files.save', $server->uuidShort), 'saveFile' => route('server.files.save', $server->uuidShort),
'csrfToken' => csrf_token(), 'csrfToken' => csrf_token(),
@ -71,7 +67,7 @@ class ServerController extends Controller
return view('server.index', [ return view('server.index', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
]); ]);
} }
@ -83,14 +79,10 @@ class ServerController extends Controller
*/ */
public function getFiles(Request $request, $uuid) public function getFiles(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('list-files', $server); $this->authorize('list-files', $server);
$node = Models\Node::find($server->node); $server->js([
Javascript::put([
'server' => collect($server->makeVisible('daemonSecret'))->only('uuid', 'uuidShort', 'daemonSecret'),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
'meta' => [ 'meta' => [
'directoryList' => route('server.files.directory-list', $server->uuidShort), 'directoryList' => route('server.files.directory-list', $server->uuidShort),
'csrftoken' => csrf_token(), 'csrftoken' => csrf_token(),
@ -108,7 +100,7 @@ class ServerController extends Controller
return view('server.files.index', [ return view('server.files.index', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
]); ]);
} }
@ -120,18 +112,14 @@ class ServerController extends Controller
*/ */
public function getAddFile(Request $request, $uuid) public function getAddFile(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('add-files', $server); $this->authorize('add-files', $server);
$node = Models\Node::find($server->node);
Javascript::put([ $server->js();
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
return view('server.files.add', [ return view('server.files.add', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/',
]); ]);
} }
@ -146,9 +134,8 @@ class ServerController extends Controller
*/ */
public function getEditFile(Request $request, $uuid, $file) public function getEditFile(Request $request, $uuid, $file)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('edit-files', $server); $this->authorize('edit-files', $server);
$node = Models\Node::find($server->node);
$fileInfo = (object) pathinfo($file); $fileInfo = (object) pathinfo($file);
$controller = new FileRepository($uuid); $controller = new FileRepository($uuid);
@ -166,15 +153,13 @@ class ServerController extends Controller
return redirect()->route('server.files.index', $uuid); return redirect()->route('server.files.index', $uuid);
} }
Javascript::put([ $server->js([
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
'stat' => $fileContent['stat'], 'stat' => $fileContent['stat'],
]); ]);
return view('server.files.edit', [ return view('server.files.edit', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
'file' => $file, 'file' => $file,
'stat' => $fileContent['stat'], 'stat' => $fileContent['stat'],
'contents' => $fileContent['file']->content, 'contents' => $fileContent['file']->content,
@ -192,9 +177,7 @@ class ServerController extends Controller
*/ */
public function getDownloadFile(Request $request, $uuid, $file) public function getDownloadFile(Request $request, $uuid, $file)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$node = Models\Node::find($server->node);
$this->authorize('download-files', $server); $this->authorize('download-files', $server);
$download = new Models\Download; $download = new Models\Download;
@ -205,69 +188,65 @@ class ServerController extends Controller
$download->save(); $download->save();
return redirect($node->scheme . '://' . $node->fqdn . ':' . $node->daemonListen . '/server/file/download/' . $download->token); return redirect($server->node->scheme . '://' . $server->node->fqdn . ':' . $server->node->daemonListen . '/server/file/download/' . $download->token);
} }
public function getAllocation(Request $request, $uuid) public function getAllocation(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('view-allocation', $server); $this->authorize('view-allocation', $server);
$node = Models\Node::find($server->node); $server->js();
Javascript::put([
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
return view('server.settings.allocation', [ return view('server.settings.allocation', [
'server' => $server, 'server' => $server->load(['allocations' => function ($query) {
'allocations' => Models\Allocation::where('assigned_to', $server->id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(), $query->orderBy('ip', 'asc');
'node' => $node, $query->orderBy('port', 'asc');
}]),
'node' => $server->node,
]); ]);
} }
public function getStartup(Request $request, $uuid) public function getStartup(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$server->load(['allocations' => function ($query) use ($server) {
$query->where('id', $server->allocation_id);
}]);
$this->authorize('view-startup', $server); $this->authorize('view-startup', $server);
$node = Models\Node::find($server->node);
$allocation = Models\Allocation::findOrFail($server->allocation);
Javascript::put([ $variables = Models\ServiceVariable::select(
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
$variables = Models\ServiceVariables::select(
'service_variables.*', 'service_variables.*',
DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_serverValue') DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_serverValue')
)->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id') )->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id')
->where('service_variables.option_id', $server->option) ->where('service_variables.option_id', $server->option_id)
->where('server_variables.server_id', $server->id) ->where('server_variables.server_id', $server->id)
->get(); ->get();
$service = Models\Service::select( $service = Models\Service::select(
DB::raw('IFNULL(service_options.executable, services.executable) as executable') DB::raw('IFNULL(service_options.executable, services.executable) as executable')
)->leftJoin('service_options', 'service_options.parent_service', '=', 'services.id') )->leftJoin('service_options', 'service_options.service_id', '=', 'services.id')
->where('service_options.id', $server->option) ->where('service_options.id', $server->option_id)
->where('services.id', $server->service) ->where('services.id', $server->service_id)
->first(); ->first();
$serverVariables = [ $allocation = $server->allocations->pop();
$ServerVariable = [
'{{SERVER_MEMORY}}' => $server->memory, '{{SERVER_MEMORY}}' => $server->memory,
'{{SERVER_IP}}' => $allocation->ip, '{{SERVER_IP}}' => $allocation->ip,
'{{SERVER_PORT}}' => $allocation->port, '{{SERVER_PORT}}' => $allocation->port,
]; ];
$processed = str_replace(array_keys($serverVariables), array_values($serverVariables), $server->startup); $processed = str_replace(array_keys($ServerVariable), array_values($ServerVariable), $server->startup);
foreach ($variables as &$variable) { foreach ($variables as &$variable) {
$replace = ($variable->user_viewable === 1) ? $variable->a_serverValue : '**'; $replace = ($variable->user_viewable === 1) ? $variable->a_serverValue : '[hidden]';
$processed = str_replace('{{' . $variable->env_variable . '}}', $replace, $processed); $processed = str_replace('{{' . $variable->env_variable . '}}', $replace, $processed);
} }
$server->js();
return view('server.settings.startup', [ return view('server.settings.startup', [
'server' => $server, 'server' => $server,
'node' => Models\Node::find($server->node), 'node' => $server->node,
'variables' => $variables->where('user_viewable', 1), 'variables' => $variables->where('user_viewable', 1),
'service' => $service, 'service' => $service,
'processedStartup' => $processed, 'processedStartup' => $processed,
@ -276,18 +255,13 @@ class ServerController extends Controller
public function getDatabases(Request $request, $uuid) public function getDatabases(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('view-databases', $server); $this->authorize('view-databases', $server);
$node = Models\Node::find($server->node); $server->js();
Javascript::put([
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
return view('server.settings.databases', [ return view('server.settings.databases', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
'databases' => Models\Database::select('databases.*', 'database_servers.host as a_host', 'database_servers.port as a_port') 'databases' => Models\Database::select('databases.*', 'database_servers.host as a_host', 'database_servers.port as a_port')
->where('server_id', $server->id) ->where('server_id', $server->id)
->join('database_servers', 'database_servers.id', '=', 'databases.db_server') ->join('database_servers', 'database_servers.id', '=', 'databases.db_server')
@ -297,24 +271,19 @@ class ServerController extends Controller
public function getSFTP(Request $request, $uuid) public function getSFTP(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('view-sftp', $server); $this->authorize('view-sftp', $server);
$node = Models\Node::find($server->node); $server->js();
Javascript::put([
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
return view('server.settings.sftp', [ return view('server.settings.sftp', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
]); ]);
} }
public function postSettingsSFTP(Request $request, $uuid) public function postSettingsSFTP(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('reset-sftp', $server); $this->authorize('reset-sftp', $server);
try { try {
@ -335,7 +304,7 @@ class ServerController extends Controller
public function postSettingsStartup(Request $request, $uuid) public function postSettingsStartup(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('edit-startup', $server); $this->authorize('edit-startup', $server);
try { try {
@ -351,9 +320,6 @@ class ServerController extends Controller
Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. Please try again.')->flash(); Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. Please try again.')->flash();
} }
return redirect()->route('server.settings', [ return redirect()->route('server.settings.startup', $uuid);
'uuid' => $uuid,
'tab' => 'tab_startup',
]);
} }
} }

View file

@ -24,11 +24,9 @@
namespace Pterodactyl\Http\Controllers\Server; namespace Pterodactyl\Http\Controllers\Server;
use DB;
use Log; use Log;
use Auth; use Auth;
use Alert; use Alert;
use Javascript;
use Pterodactyl\Models; use Pterodactyl\Models;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
@ -50,73 +48,47 @@ class SubuserController extends Controller
public function getIndex(Request $request, $uuid) public function getIndex(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid)->load('subusers.user');
$this->authorize('list-subusers', $server); $this->authorize('list-subusers', $server);
$node = Models\Node::find($server->node);
Javascript::put([ $server->js();
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
return view('server.users.index', [ return view('server.users.index', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
'subusers' => Models\Subuser::select('subusers.*', 'users.email', 'users.username', 'users.use_totp') 'subusers' => $server->subusers,
->join('users', 'users.id', '=', 'subusers.user_id')
->where('server_id', $server->id)
->get(),
]); ]);
} }
public function getView(Request $request, $uuid, $id) public function getView(Request $request, $uuid, $id)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid)->load('node');
$this->authorize('view-subuser', $server); $this->authorize('view-subuser', $server);
$node = Models\Node::find($server->node);
Javascript::put([ $subuser = Models\Subuser::with('permissions', 'user')
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), ->where('server_id', $server->id)->findOrFail($id);
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
$subuser = Models\Subuser::select('subusers.*', 'users.email as a_userEmail') $server->js();
->join('users', 'users.id', '=', 'subusers.user_id')
->where(DB::raw('md5(subusers.id)'), $id)->where('subusers.server_id', $server->id)
->first();
if (! $subuser) {
abort(404);
}
$permissions = [];
$modelPermissions = Models\Permission::select('permission')
->where('user_id', $subuser->user_id)->where('server_id', $server->id)
->get();
foreach ($modelPermissions as &$perm) {
$permissions[$perm->permission] = true;
}
return view('server.users.view', [ return view('server.users.view', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
'subuser' => $subuser, 'subuser' => $subuser,
'permissions' => $permissions, 'permissions' => $subuser->permissions->mapWithKeys(function ($item, $key) {
return [$item->permission => true];
}),
]); ]);
} }
public function postView(Request $request, $uuid, $id) public function postView(Request $request, $uuid, $id)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('edit-subuser', $server); $this->authorize('edit-subuser', $server);
$subuser = Models\Subuser::where(DB::raw('md5(id)'), $id)->where('server_id', $server->id)->first(); $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id);
try { try {
if (! $subuser) { if ($subuser->user_id === Auth::user()->id) {
throw new DisplayException('Unable to locate a subuser by that ID.');
} elseif ($subuser->user_id === Auth::user()->id) {
throw new DisplayException('You are not authorized to edit you own account.'); throw new DisplayException('You are not authorized to edit you own account.');
} }
@ -148,36 +120,31 @@ class SubuserController extends Controller
public function getNew(Request $request, $uuid) public function getNew(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('create-subuser', $server); $this->authorize('create-subuser', $server);
$node = Models\Node::find($server->node); $server->js();
Javascript::put([
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
return view('server.users.new', [ return view('server.users.new', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
]); ]);
} }
public function postNew(Request $request, $uuid) public function postNew(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('create-subuser', $server); $this->authorize('create-subuser', $server);
try { try {
$repo = new SubuserRepository; $repo = new SubuserRepository;
$id = $repo->create($server->id, $request->except([ $subuser = $repo->create($server->id, $request->only([
'_token', 'permissions', 'email',
])); ]));
Alert::success('Successfully created new subuser.')->flash(); Alert::success('Successfully created new subuser.')->flash();
return redirect()->route('server.subusers.view', [ return redirect()->route('server.subusers.view', [
'uuid' => $uuid, 'uuid' => $uuid,
'id' => md5($id), 'id' => $subuser->id,
]); ]);
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->route('server.subusers.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); return redirect()->route('server.subusers.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput();
@ -193,14 +160,11 @@ class SubuserController extends Controller
public function deleteSubuser(Request $request, $uuid, $id) public function deleteSubuser(Request $request, $uuid, $id)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('delete-subuser', $server); $this->authorize('delete-subuser', $server);
try { try {
$subuser = Models\Subuser::select('id')->where(DB::raw('md5(id)'), $id)->where('server_id', $server->id)->first(); $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id);
if (! $subuser) {
throw new DisplayException('No subuser by that ID was found on the system.');
}
$repo = new SubuserRepository; $repo = new SubuserRepository;
$repo->delete($subuser->id); $repo->delete($subuser->id);

View file

@ -26,7 +26,6 @@ namespace Pterodactyl\Http\Controllers\Server;
use Log; use Log;
use Alert; use Alert;
use Javascript;
use Pterodactyl\Models; use Pterodactyl\Models;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Repositories; use Pterodactyl\Repositories;
@ -43,19 +42,14 @@ class TaskController extends Controller
public function getIndex(Request $request, $uuid) public function getIndex(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid)->load('tasks');
$this->authorize('list-tasks', $server); $this->authorize('list-tasks', $server);
$node = Models\Node::find($server->node); $server->js();
Javascript::put([
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
return view('server.tasks.index', [ return view('server.tasks.index', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
'tasks' => Models\Task::where('server', $server->id)->get(), 'tasks' => $server->tasks,
'actions' => [ 'actions' => [
'command' => trans('server.tasks.actions.command'), 'command' => trans('server.tasks.actions.command'),
'power' => trans('server.tasks.actions.power'), 'power' => trans('server.tasks.actions.power'),
@ -65,24 +59,19 @@ class TaskController extends Controller
public function getNew(Request $request, $uuid) public function getNew(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('create-task', $server); $this->authorize('create-task', $server);
$node = Models\Node::find($server->node); $server->js();
Javascript::put([
'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']),
'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'),
]);
return view('server.tasks.new', [ return view('server.tasks.new', [
'server' => $server, 'server' => $server,
'node' => $node, 'node' => $server->node,
]); ]);
} }
public function postNew(Request $request, $uuid) public function postNew(Request $request, $uuid)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid);
$this->authorize('create-task', $server); $this->authorize('create-task', $server);
try { try {
@ -106,12 +95,11 @@ class TaskController extends Controller
public function deleteTask(Request $request, $uuid, $id) public function deleteTask(Request $request, $uuid, $id)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid)->load('tasks');
$this->authorize('delete-task', $server); $this->authorize('delete-task', $server);
$task = Models\Task::findOrFail($id); $task = $server->tasks->where('id', $id)->first();
if (! $task) {
if (! $task || $server->id !== $task->server) {
return response()->json([ return response()->json([
'error' => 'No task by that ID was found associated with this server.', 'error' => 'No task by that ID was found associated with this server.',
], 404); ], 404);
@ -133,12 +121,11 @@ class TaskController extends Controller
public function toggleTask(Request $request, $uuid, $id) public function toggleTask(Request $request, $uuid, $id)
{ {
$server = Models\Server::getByUUID($uuid); $server = Models\Server::byUuid($uuid)->load('tasks');
$this->authorize('toggle-task', $server); $this->authorize('toggle-task', $server);
$task = Models\Task::findOrFail($id); $task = $server->tasks->where('id', $id)->first();
if (! $task) {
if (! $task || $server->id !== $task->server) {
return response()->json([ return response()->json([
'error' => 'No task by that ID was found associated with this server.', 'error' => 'No task by that ID was found associated with this server.',
], 404); ], 404);

View file

@ -17,7 +17,9 @@ class Kernel extends HttpKernel
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class, \Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Pterodactyl\Http\Middleware\LanguageMiddleware::class, \Pterodactyl\Http\Middleware\LanguageMiddleware::class,
\Fideloper\Proxy\TrustProxies::class,
]; ];
/** /**
@ -51,6 +53,7 @@ class Kernel extends HttpKernel
'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class,
'server' => \Pterodactyl\Http\Middleware\CheckServer::class, 'server' => \Pterodactyl\Http\Middleware\CheckServer::class,
'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class,
'daemon' => \Pterodactyl\Http\Middleware\DaemonAuthenticate::class,
'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, 'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class,

View file

@ -121,7 +121,7 @@ class APISecretToken extends Authorization
// Log the Route Access // Log the Route Access
APILogService::log($request, null, true); APILogService::log($request, null, true);
return Auth::loginUsingId($key->user); return Auth::loginUsingId($key->user_id);
} }
protected function _generateHMAC($body, $key) protected function _generateHMAC($body, $key)

View file

@ -26,6 +26,7 @@ namespace Pterodactyl\Http\Middleware;
use Auth; use Auth;
use Closure; use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
class CheckServer class CheckServer
@ -37,22 +38,22 @@ class CheckServer
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if (! Auth::user()) { if (! Auth::user()) {
return redirect()->guest('auth/login'); return redirect()->guest('auth/login');
} }
$server = Server::getByUUID($request->route()->server); $server = Server::byUuid($request->route()->server);
if (! $server) { if (! $server) {
return response()->view('errors.404', [], 404); return response()->view('errors.404', [], 404);
} }
if ($server->suspended === 1) { if ($server->suspended) {
return response()->view('errors.suspended', [], 403); return response()->view('errors.suspended', [], 403);
} }
if ($server->installed !== 1) { if (! $server->installed) {
return response()->view('errors.installing', [], 403); return response()->view('errors.installing', [], 403);
} }

View file

@ -22,41 +22,50 @@
* SOFTWARE. * SOFTWARE.
*/ */
namespace Pterodactyl\Listeners; namespace Pterodactyl\Http\Middleware;
use Carbon; use Closure;
use Pterodactyl\Jobs\DeleteServer; use Pterodactyl\Models\Node;
use Pterodactyl\Jobs\SuspendServer; use Illuminate\Contracts\Auth\Guard;
use Pterodactyl\Events\ServerDeleted;
use Illuminate\Foundation\Bus\DispatchesJobs;
class DeleteServerListener class DaemonAuthenticate
{ {
use DispatchesJobs; /**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/** /**
* Create the event listener. * Create a new filter instance.
* *
* @param Guard $auth
* @return void * @return void
*/ */
public function __construct() public function __construct(Guard $auth)
{ {
// $this->auth = $auth;
} }
/** /**
* Handle the event. * Handle an incoming request.
* *
* @param DeleteServerEvent $event * @param \Illuminate\Http\Request $request
* @return void * @param \Closure $next
* @return mixed
*/ */
public function handle(ServerDeleted $event) public function handle($request, Closure $next)
{ {
$this->dispatch((new SuspendServer($event->server))->onQueue(env('QUEUE_HIGH', 'high'))); if (! $request->header('X-Access-Node')) {
$this->dispatch( return abort(403);
(new DeleteServer($event->server)) }
->delay(Carbon::now()->addMinutes(env('APP_DELETE_MINUTES', 10)))
->onQueue(env('QUEUE_STANDARD', 'standard')) $node = Node::where('daemonSecret', $request->header('X-Access-Node'))->first();
); if (! $node) {
return abort(404);
}
return $next($request);
} }
} }

View file

@ -13,6 +13,7 @@ class VerifyCsrfToken extends BaseVerifier
*/ */
protected $except = [ protected $except = [
'remote/*', 'remote/*',
'daemon/*',
'api/*', 'api/*',
]; ];
} }

View file

@ -144,7 +144,7 @@ class AdminRoutes
]); ]);
$router->post('/new/service-options', [ $router->post('/new/service-options', [
'uses' => 'Admin\ServersController@postNewServerServiceOptions', 'uses' => 'Admin\ServersController@postNewServerServiceOption',
]); ]);
$router->post('/new/option-details', [ $router->post('/new/option-details', [

View file

@ -51,9 +51,13 @@ class AuthRoutes
'uses' => 'Auth\LoginController@login', 'uses' => 'Auth\LoginController@login',
]); ]);
// Determine if we need to ask for a TOTP Token $router->get('login/totp', [
'as' => 'auth.totp',
'uses' => 'Auth\LoginController@totp',
]);
$router->post('login/totp', [ $router->post('login/totp', [
'uses' => 'Auth\LoginController@checkTotp', 'uses' => 'Auth\LoginController@totpCheckpoint',
]); ]);
// Show Password Reset Form // Show Password Reset Form

View file

@ -89,6 +89,7 @@ class BaseRoutes
]); ]);
$router->delete('/revoke/{key}', [ $router->delete('/revoke/{key}', [
'as' => 'account.api.revoke',
'uses' => 'Base\APIController@revoke', 'uses' => 'Base\APIController@revoke',
]); ]);
}); });

View file

@ -30,7 +30,7 @@ class DaemonRoutes
{ {
public function map(Router $router) public function map(Router $router)
{ {
$router->group(['prefix' => 'daemon'], function () use ($router) { $router->group(['prefix' => 'daemon', 'middleware' => 'daemon'], function () use ($router) {
$router->get('services', [ $router->get('services', [
'as' => 'daemon.services', 'as' => 'daemon.services',
'uses' => 'Daemon\ServiceController@list', 'uses' => 'Daemon\ServiceController@list',
@ -40,6 +40,15 @@ class DaemonRoutes
'as' => 'remote.install', 'as' => 'remote.install',
'uses' => 'Daemon\ServiceController@pull', 'uses' => 'Daemon\ServiceController@pull',
]); ]);
$router->get('packs/pull/{uuid}', [
'as' => 'daemon.pack.pull',
'uses' => 'Daemon\PackController@pull',
]);
$router->get('packs/pull/{uuid}/hash', [
'as' => 'daemon.pack.hash',
'uses' => 'Daemon\PackController@hash',
]);
}); });
} }
} }

View file

@ -42,11 +42,6 @@ class RemoteRoutes
'uses' => 'Remote\RemoteController@postInstall', 'uses' => 'Remote\RemoteController@postInstall',
]); ]);
$router->post('event', [
'as' => 'remote.event',
'uses' => 'Remote\RemoteController@event',
]);
$router->get('configuration/{token}', [ $router->get('configuration/{token}', [
'as' => 'remote.configuration', 'as' => 'remote.configuration',
'uses' => 'Remote\RemoteController@getConfiguration', 'uses' => 'Remote\RemoteController@getConfiguration',

View file

@ -30,6 +30,13 @@ class ServerRoutes
{ {
public function map(Router $router) public function map(Router $router)
{ {
// Returns Server Status
$router->get('/server/{server}/ajax/status', [
'middleware' => ['auth', 'csrf'],
'as' => 'server.ajax.status',
'uses' => 'Server\AjaxController@getStatus',
]);
$router->group([ $router->group([
'prefix' => 'server/{server}', 'prefix' => 'server/{server}',
'middleware' => [ 'middleware' => [
@ -45,12 +52,6 @@ class ServerRoutes
'uses' => 'Server\ServerController@getIndex', 'uses' => 'Server\ServerController@getIndex',
]); ]);
// Settings
$router->get('/settings', [
'as' => 'server.settings',
'uses' => 'Server\ServerController@getSettings',
]);
$router->get('/settings/databases', [ $router->get('/settings/databases', [
'as' => 'server.settings.databases', 'as' => 'server.settings.databases',
'uses' => 'Server\ServerController@getDatabases', 'uses' => 'Server\ServerController@getDatabases',
@ -135,6 +136,7 @@ class ServerRoutes
]); ]);
$router->delete('users/delete/{id}', [ $router->delete('users/delete/{id}', [
'as' => 'server.subusers.delete',
'uses' => 'Server\SubuserController@deleteSubuser', 'uses' => 'Server\SubuserController@deleteSubuser',
]); ]);
@ -169,12 +171,6 @@ class ServerRoutes
// Assorted AJAX Routes // Assorted AJAX Routes
$router->group(['prefix' => 'ajax'], function ($server) use ($router) { $router->group(['prefix' => 'ajax'], function ($server) use ($router) {
// Returns Server Status
$router->get('status', [
'as' => 'server.ajax.status',
'uses' => 'Server\AjaxController@getStatus',
]);
// Sets the Default Connection for the Server // Sets the Default Connection for the Server
$router->post('set-primary', [ $router->post('set-primary', [
'uses' => 'Server\AjaxController@postSetPrimary', 'uses' => 'Server\AjaxController@postSetPrimary',

View file

@ -48,4 +48,14 @@ class APIKey extends Model
* @var array * @var array
*/ */
protected $guarded = ['id', 'created_at', 'updated_at']; protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Gets the permissions associated with a key.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function permissions()
{
return $this->hasMany(APIPermission::class, 'key_id');
}
} }

View file

@ -48,8 +48,40 @@ class Allocation extends Model
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'node' => 'integer', 'node_id' => 'integer',
'port' => 'integer', 'port' => 'integer',
'assigned_to' => 'integer', 'server_id' => 'integer',
]; ];
/**
* Accessor to automatically provide the IP alias if defined.
*
* @param null|string $value
* @return string
*/
public function getAliasAttribute($value)
{
return (is_null($this->ip_alias)) ? $this->ip : $this->ip_alias;
}
/**
* Accessor to quickly determine if this allocation has an alias.
*
* @param null|string $value
* @return bool
*/
public function getHasAliasAttribute($value)
{
return ! is_null($this->ip_alias);
}
/**
* Gets information for the server associated with this allocation.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function server()
{
return $this->belongsTo(Server::class);
}
} }

View file

@ -55,7 +55,27 @@ class Database extends Model
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'server' => 'integer', 'server_id' => 'integer',
'db_server' => 'integer', 'db_server' => 'integer',
]; ];
/**
* Gets the host database server associated with a database.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function host()
{
return $this->belongsTo(DatabaseServer::class, 'db_server');
}
/**
* Gets the server associated with a database.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function server()
{
return $this->belongsTo(Server::class);
}
} }

View file

@ -59,4 +59,24 @@ class DatabaseServer extends Model
'server_id' => 'integer', 'server_id' => 'integer',
'db_server' => 'integer', 'db_server' => 'integer',
]; ];
/**
* Gets the node associated with a database host.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function node()
{
return $this->belongsTo(Node::class, 'linked_node');
}
/**
* Gets the databases assocaited with this host.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function databases()
{
return $this->hasMany(Database::class, 'db_server');
}
} }

View file

@ -41,4 +41,24 @@ class Location extends Model
* @var array * @var array
*/ */
protected $guarded = ['id', 'created_at', 'updated_at']; protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Gets the nodes in a specificed location.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function nodes()
{
return $this->hasMany(Node::class);
}
/**
* Gets the servers within a given location.
*
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function servers()
{
return $this->hasManyThrough(Server::class, Node::class);
}
} }

View file

@ -26,9 +26,12 @@ namespace Pterodactyl\Models;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Node extends Model class Node extends Model
{ {
use Notifiable;
/** /**
* The table associated with the model. * The table associated with the model.
* *
@ -50,7 +53,7 @@ class Node extends Model
*/ */
protected $casts = [ protected $casts = [
'public' => 'integer', 'public' => 'integer',
'location' => 'integer', 'location_id' => 'integer',
'memory' => 'integer', 'memory' => 'integer',
'disk' => 'integer', 'disk' => 'integer',
'daemonListen' => 'integer', 'daemonListen' => 'integer',
@ -58,64 +61,33 @@ class Node extends Model
]; ];
/** /**
* Fields that are not mass assignable. * Fields that are mass assignable.
* *
* @var array * @var array
*/ */
protected $guarded = ['id', 'created_at', 'updated_at']; protected $fillable = [
'public', 'name', 'location_id',
'fqdn', 'scheme', 'memory',
'memory_overallocate', 'disk',
'disk_overallocate', 'upload_size',
'daemonSecret', 'daemonBase',
'daemonSFTP', 'daemonListen',
];
/** /**
* @var array * Return an instance of the Guzzle client for this specific node.
*/
protected static $guzzle = [];
/**
* @var array
*/
protected static $nodes = [];
/**
* Returns an instance of the database object for the requested node ID.
* *
* @param int $id * @param array $headers
* @return \Illuminate\Database\Eloquent\Model
*/
public static function getByID($id)
{
// The Node is already cached.
if (array_key_exists($id, self::$nodes)) {
return self::$nodes[$id];
}
self::$nodes[$id] = self::where('id', $id)->first();
return self::$nodes[$id];
}
/**
* Returns a Guzzle Client for the node in question.
*
* @param int $node
* @return \GuzzleHttp\Client * @return \GuzzleHttp\Client
*/ */
public static function guzzleRequest($node) public function guzzleClient($headers = [])
{ {
return new Client([
// The Guzzle Client is cached already. 'base_uri' => sprintf('%s://%s:%s/', $this->scheme, $this->fqdn, $this->daemonListen),
if (array_key_exists($node, self::$guzzle)) { 'timeout' => env('GUZZLE_TIMEOUT', 5.0),
return self::$guzzle[$node]; 'connect_timeout' => env('GUZZLE_CONNECT_TIMEOUT', 3.0),
} 'headers' => $headers,
$nodeData = self::getByID($node);
self::$guzzle[$node] = new Client([
'base_uri' => sprintf('%s://%s:%s/', $nodeData->scheme, $nodeData->fqdn, $nodeData->daemonListen),
'timeout' => 5.0,
'connect_timeout' => 3.0,
]); ]);
return self::$guzzle[$node];
} }
/** /**
@ -167,11 +139,36 @@ class Node extends Model
'keys' => [$this->daemonSecret], 'keys' => [$this->daemonSecret],
]; ];
$json_options = JSON_UNESCAPED_SLASHES; return json_encode($config, ($pretty) ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES);
if ($pretty) {
$json_options |= JSON_PRETTY_PRINT;
} }
return json_encode($config, $json_options); /**
* Gets the location associated with a node.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function location()
{
return $this->belongsTo(Location::class);
}
/**
* Gets the servers associated with a node.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servers()
{
return $this->hasMany(Server::class);
}
/**
* Gets the allocations associated with a node.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function allocations()
{
return $this->hasMany(Allocation::class);
} }
} }

View file

@ -28,6 +28,13 @@ use Illuminate\Database\Eloquent\Model;
class Permission extends Model class Permission extends Model
{ {
/**
* Should timestamps be used on this model.
*
* @var bool
*/
public $timestamps = false;
/** /**
* The table associated with the model. * The table associated with the model.
* *
@ -48,16 +55,29 @@ class Permission extends Model
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'user_id' => 'integer', 'subuser_id' => 'integer',
'server_id' => 'integer',
]; ];
/**
* Find permission by permission node.
*
* @param \Illuminate\Database\Query\Builder $query
* @param string $permission
* @return \Illuminate\Database\Query\Builder
*/
public function scopePermission($query, $permission) public function scopePermission($query, $permission)
{ {
return $query->where('permission', $permission); return $query->where('permission', $permission);
} }
public function scopeServer($query, $server) /**
* Filter permission by server.
*
* @param \Illuminate\Database\Query\Builder $query
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Database\Query\Builder
*/
public function scopeServer($query, Server $server)
{ {
return $query->where('server_id', $server->id); return $query->where('server_id', $server->id);
} }

View file

@ -25,12 +25,15 @@
namespace Pterodactyl\Models; namespace Pterodactyl\Models;
use Auth; use Auth;
use Cache;
use Javascript;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class Server extends Model class Server extends Model
{ {
use SoftDeletes; use Notifiable, SoftDeletes;
/** /**
* The table associated with the model. * The table associated with the model.
@ -66,96 +69,22 @@ class Server extends Model
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'node' => 'integer', 'node_id' => 'integer',
'suspended' => 'integer', 'suspended' => 'integer',
'owner' => 'integer', 'owner_id' => 'integer',
'memory' => 'integer', 'memory' => 'integer',
'swap' => 'integer', 'swap' => 'integer',
'disk' => 'integer', 'disk' => 'integer',
'io' => 'integer', 'io' => 'integer',
'cpu' => 'integer', 'cpu' => 'integer',
'oom_disabled' => 'integer', 'oom_disabled' => 'integer',
'port' => 'integer', 'allocation_id' => 'integer',
'service' => 'integer', 'service_id' => 'integer',
'option' => 'integer', 'option_id' => 'integer',
'pack_id' => 'integer',
'installed' => 'integer', 'installed' => 'integer',
]; ];
/**
* @var array
*/
protected static $serverUUIDInstance = [];
/**
* @var mixed
*/
protected static $user;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
self::$user = Auth::user();
}
/**
* Determine if we need to change the server's daemonSecret value to
* match that of the user if they are a subuser.
*
* @param Illuminate\Database\Eloquent\Model\Server $server
* @return string
*/
public static function getUserDaemonSecret(Server $server)
{
if (self::$user->id === $server->owner || self::$user->root_admin === 1) {
return $server->daemonSecret;
}
$subuser = Subuser::where('server_id', $server->id)->where('user_id', self::$user->id)->first();
if (is_null($subuser)) {
return null;
}
return $subuser->daemonSecret;
}
/**
* Returns array of all servers owned by the logged in user.
* Returns all users servers if user is a root admin.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getUserServers($paginate = null)
{
$query = self::select(
'servers.*',
'nodes.name as nodeName',
'locations.short as a_locationShort',
'allocations.ip',
'allocations.ip_alias',
'allocations.port',
'services.name as a_serviceName',
'service_options.name as a_serviceOptionName'
)->join('nodes', 'servers.node', '=', 'nodes.id')
->join('locations', 'nodes.location', '=', 'locations.id')
->join('services', 'servers.service', '=', 'services.id')
->join('service_options', 'servers.option', '=', 'service_options.id')
->join('allocations', 'servers.allocation', '=', 'allocations.id');
if (self::$user->root_admin !== 1) {
$query->whereIn('servers.id', Subuser::accessServers());
}
if (is_numeric($paginate)) {
return $query->paginate($paginate);
}
return $query->get();
}
/** /**
* Returns a single server specified by UUID. * Returns a single server specified by UUID.
* DO NOT USE THIS TO MODIFY SERVER DETAILS OR SAVE THOSE DETAILS. * DO NOT USE THIS TO MODIFY SERVER DETAILS OR SAVE THOSE DETAILS.
@ -164,30 +93,26 @@ class Server extends Model
* @param string $uuid The Short-UUID of the server to return an object about. * @param string $uuid The Short-UUID of the server to return an object about.
* @return \Illuminate\Database\Eloquent\Collection * @return \Illuminate\Database\Eloquent\Collection
*/ */
public static function getByUUID($uuid) public static function byUuid($uuid)
{ {
if (array_key_exists($uuid, self::$serverUUIDInstance)) { // Results are cached because we call this functions a few times on page load.
return self::$serverUUIDInstance[$uuid]; $result = Cache::remember('Server.byUuid.' . $uuid . Auth::user()->uuid, 60, function () use ($uuid) {
$query = self::with('service', 'node')->where(function ($q) use ($uuid) {
$q->where('uuidShort', $uuid)->orWhere('uuid', $uuid);
});
if (! Auth::user()->isRootAdmin()) {
$query->whereIn('id', Auth::user()->serverAccessArray());
} }
$query = self::select('servers.*', 'services.file as a_serviceFile') return $query->first();
->join('services', 'services.id', '=', 'servers.service') });
->where('uuidShort', $uuid)
->orWhere('uuid', $uuid);
if (self::$user->root_admin !== 1) {
$query->whereIn('servers.id', Subuser::accessServers());
}
$result = $query->first();
if (! is_null($result)) { if (! is_null($result)) {
$result->daemonSecret = self::getUserDaemonSecret($result); $result->daemonSecret = Auth::user()->daemonToken($result);
} }
self::$serverUUIDInstance[$uuid] = $result; return $result;
return self::$serverUUIDInstance[$uuid];
} }
/** /**
@ -196,15 +121,164 @@ class Server extends Model
* @param string $uuid * @param string $uuid
* @return array * @return array
*/ */
public static function getGuzzleHeaders($uuid) public function guzzleHeaders()
{ {
if (array_key_exists($uuid, self::$serverUUIDInstance)) {
return [ return [
'X-Access-Server' => self::$serverUUIDInstance[$uuid]->uuid, 'X-Access-Server' => $this->uuid,
'X-Access-Token' => self::$serverUUIDInstance[$uuid]->daemonSecret, 'X-Access-Token' => Auth::user()->daemonToken($this),
]; ];
} }
return []; /**
* Return an instance of the Guzzle client for this specific server using defined access token.
*
* @return \GuzzleHttp\Client
*/
public function guzzleClient()
{
return $this->node->guzzleClient($this->guzzleHeaders());
}
/**
* Returns javascript object to be embedded on server view pages with relevant information.
*
* @return \Laracasts\Utilities\JavaScript\JavaScriptFacade
*/
public function js($additional = null, $overwrite = null)
{
$response = [
'server' => collect($this->makeVisible('daemonSecret'))->only([
'uuid',
'uuidShort',
'daemonSecret',
'username',
]),
'node' => collect($this->node)->only([
'fqdn',
'scheme',
'daemonListen',
]),
];
if (is_array($additional)) {
$response = array_merge($response, $additional);
}
if (is_array($overwrite)) {
$response = $overwrite;
}
return Javascript::put($response);
}
/**
* Gets the user who owns the server.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class, 'owner_id');
}
/**
* Gets the subusers associated with a server.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function subusers()
{
return $this->hasMany(Subuser::class);
}
/**
* Gets the default allocation for a server.
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function allocation()
{
return $this->hasOne(Allocation::class, 'id', 'allocation_id');
}
/**
* Gets all allocations associated with this server.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function allocations()
{
return $this->hasMany(Allocation::class, 'server_id');
}
/**
* Gets information for the pack associated with this server.
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function pack()
{
return $this->hasOne(ServicePack::class, 'id', 'pack_id');
}
/**
* Gets information for the service associated with this server.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function service()
{
return $this->belongsTo(Service::class);
}
/**
* Gets information for the service option associated with this server.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function option()
{
return $this->belongsTo(ServiceOption::class);
}
/**
* Gets information for the service variables associated with this server.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function variables()
{
return $this->hasMany(ServerVariable::class);
}
/**
* Gets information for the node associated with this server.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function node()
{
return $this->belongsTo(Node::class);
}
/**
* Gets information for the tasks associated with this server.
*
* @TODO adjust server column in tasks to be server_id
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tasks()
{
return $this->hasMany(Task::class, 'server', 'id');
}
/**
* Gets all databases associated with a server.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function databases()
{
return $this->hasMany(Database::class);
} }
} }

View file

@ -26,7 +26,7 @@ namespace Pterodactyl\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class ServerVariables extends Model class ServerVariable extends Model
{ {
/** /**
* The table associated with the model. * The table associated with the model.
@ -51,4 +51,14 @@ class ServerVariables extends Model
'server_id' => 'integer', 'server_id' => 'integer',
'variable_id' => 'integer', 'variable_id' => 'integer',
]; ];
/**
* Returns information about a given variables parent.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function variable()
{
return $this->belongsTo(ServiceVariable::class, 'variable_id');
}
} }

View file

@ -36,9 +36,44 @@ class Service extends Model
protected $table = 'services'; protected $table = 'services';
/** /**
* Fields that are not mass assignable. * Fields that are mass assignable.
* *
* @var array * @var array
*/ */
protected $guarded = ['id', 'created_at', 'updated_at']; protected $fillable = [
'name', 'description', 'file', 'executable', 'startup',
];
/**
* Gets all service options associated with this service.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function options()
{
return $this->hasMany(ServiceOption::class);
}
/**
* Returns all of the packs associated with a service, regardless of the service option.
*
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function packs()
{
return $this->hasManyThrough(
'Pterodactyl\Models\ServicePack', 'Pterodactyl\Models\ServiceOption',
'service_id', 'option_id'
);
}
/**
* Gets all servers associated with this service.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servers()
{
return $this->hasMany(Server::class);
}
} }

View file

@ -0,0 +1,115 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Models;
use Illuminate\Database\Eloquent\Model;
class ServiceOption extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'service_options';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'service_id' => 'integer',
];
/**
* Returns the display executable for the option and will use the parent
* service one if the option does not have one defined.
*
* @return string
*/
public function getDisplayExecutableAttribute($value)
{
return (is_null($this->executable)) ? $this->service->executable : $this->executable;
}
/**
* Returns the display startup string for the option and will use the parent
* service one if the option does not have one defined.
*
* @return string
*/
public function getDisplayStartupAttribute($value)
{
return (is_null($this->startup)) ? $this->service->startup : $this->startup;
}
/**
* Gets service associated with a service option.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function service()
{
return $this->belongsTo(Service::class);
}
/**
* Gets all servers associated with this service option.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servers()
{
return $this->hasMany(Server::class, 'option_id');
}
/**
* Gets all variables associated with this service.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function variables()
{
return $this->hasMany(ServiceVariable::class, 'option_id');
}
/**
* Gets all packs associated with this service.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function packs()
{
return $this->hasMany(ServicePack::class, 'option_id');
}
}

View file

@ -56,4 +56,14 @@ class ServicePack extends Model
'selectable' => 'boolean', 'selectable' => 'boolean',
'visible' => 'boolean', 'visible' => 'boolean',
]; ];
/**
* Gets option associated with a service pack.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function option()
{
return $this->belongsTo(ServiceOption::class);
}
} }

View file

@ -26,7 +26,7 @@ namespace Pterodactyl\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class ServiceVariables extends Model class ServiceVariable extends Model
{ {
/** /**
* The table associated with the model. * The table associated with the model.
@ -53,4 +53,9 @@ class ServiceVariables extends Model
'user_editable' => 'integer', 'user_editable' => 'integer',
'required' => 'integer', 'required' => 'integer',
]; ];
public function serverVariable()
{
return $this->hasMany(ServerVariable::class, 'variable_id');
}
} }

View file

@ -24,11 +24,13 @@
namespace Pterodactyl\Models; namespace Pterodactyl\Models;
use Auth;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Subuser extends Model class Subuser extends Model
{ {
use Notifiable;
/** /**
* The table associated with the model. * The table associated with the model.
* *
@ -61,27 +63,32 @@ class Subuser extends Model
]; ];
/** /**
* @var mixed * Gets the server associated with a subuser.
*/
protected static $user;
/**
* Constructor.
*/
public function __construct()
{
self::$user = Auth::user();
}
/**
* Returns an array of each server ID that the user has access to.
* *
* @return array * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
public static function accessServers() public function server()
{ {
$union = self::select('server_id')->where('user_id', self::$user->id); return $this->belongsTo(Server::class);
}
return Server::select('id')->where('owner', self::$user->id)->union($union)->pluck('id'); /**
* Gets the user associated with a subuser.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Gets the permissions associated with a subuser.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function permissions()
{
return $this->hasMany(Permission::class);
} }
} }

View file

@ -87,16 +87,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
*/ */
protected $hidden = ['password', 'remember_token', 'totp_secret']; protected $hidden = ['password', 'remember_token', 'totp_secret'];
/**
* Determines if a user has permissions.
*
* @return bool
*/
public function permissions()
{
return $this->hasMany(Permission::class);
}
/** /**
* Enables or disables TOTP on an account if the token is valid. * Enables or disables TOTP on an account if the token is valid.
* *
@ -105,7 +95,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
*/ */
public function toggleTotp($token) public function toggleTotp($token)
{ {
if (! Google2FA::verifyKey($this->totp_secret, $token)) { if (! Google2FA::verifyKey($this->totp_secret, $token, 1)) {
return false; return false;
} }
@ -156,4 +146,73 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
{ {
return $this->root_admin === 1; return $this->root_admin === 1;
} }
/**
* Returns the user's daemon secret for a given server.
* @param Server $server \Pterodactyl\Models\Server
* @return null|string
*/
public function daemonToken(Server $server)
{
if ($this->id === $server->owner_id || $this->isRootAdmin()) {
return $server->daemonSecret;
}
$subuser = Subuser::where('server_id', $server->id)->where('user_id', $this->id)->first();
if (is_null($subuser)) {
return null;
}
return $subuser->daemonSecret;
}
/**
* 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()
{
$union = Subuser::select('server_id')->where('user_id', $this->id);
return Server::select('id')->where('owner_id', $this->id)->union($union)->pluck('id')->all();
}
/**
* Returns an array of all servers a user is able to access.
* Note: does not account for user admin status.
*
* @return Collection
*/
public function serverAccessCollection($paginate = null, $load = ['service', 'node', 'allocation'])
{
$query = Server::with($load);
if (! $this->isRootAdmin()) {
$query->whereIn('id', $this->serverAccessArray());
}
return (is_numeric($paginate)) ? $query->paginate($paginate) : $query->get();
}
/**
* Returns all permissions that a user has.
*
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function permissions()
{
return $this->hasManyThrough(Permission::class, Subuser::class);
}
/**
* Returns all servers that a user owns.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servers()
{
return $this->hasMany(Server::class, 'owner_id');
}
} }

View file

@ -38,16 +38,16 @@ class AccountCreated extends Notification implements ShouldQueue
* *
* @var string * @var string
*/ */
public $token; public $user;
/** /**
* Create a new notification instance. * Create a new notification instance.
* *
* @return void * @return void
*/ */
public function __construct($token) public function __construct(array $user)
{ {
$this->token = $token; $this->user = (object) $user;
} }
/** /**
@ -69,9 +69,16 @@ class AccountCreated extends Notification implements ShouldQueue
*/ */
public function toMail($notifiable) public function toMail($notifiable)
{ {
return (new MailMessage) $message = (new MailMessage)
->greeting('Hello ' . $this->user->name . '!')
->line('You are recieving this email because an account has been created for you on Pterodactyl Panel.') ->line('You are recieving this email because an account has been created for you on Pterodactyl Panel.')
->line('Email: ' . $notifiable->email) ->line('Username: ' . $this->user->username)
->action('Setup Your Account', url('/auth/password/reset/' . $this->token . '?email=' . $notifiable->email)); ->line('Email: ' . $notifiable->email);
if (! is_null($this->user->token)) {
return $message->action('Setup Your Account', url('/auth/password/reset/' . $this->user->token . '?email=' . $notifiable->email));
}
return $message;
} }
} }

View file

@ -0,0 +1,74 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class AddedToServer extends Notification implements ShouldQueue
{
use Queueable;
public $server;
/**
* Create a new notification instance.
*
* @param array $server
* @return void
*/
public function __construct(array $server)
{
$this->server = (object) $server;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->greeting('Hello ' . $this->server->user . '!')
->line('You have been added as a subuser for the following server, allowing you certain control over the server.')
->line('Server Name: ' . $this->server->name)
->action('Visit Server', route('server.index', $this->server->uuidShort));
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class RemovedFromServer extends Notification implements ShouldQueue
{
use Queueable;
public $server;
/**
* Create a new notification instance.
*
* @param array $server
* @return void
*/
public function __construct(array $server)
{
$this->server = (object) $server;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->error()
->greeting('Hello ' . $this->server->user . '.')
->line('You have been removed as a subuser for the following server.')
->line('Server Name: ' . $this->server->name)
->action('Visit Panel', route('index'));
}
}

View file

@ -72,20 +72,7 @@ class SendPasswordReset extends Notification implements ShouldQueue
return (new MailMessage) return (new MailMessage)
->subject('Reset Password') ->subject('Reset Password')
->line('You are receiving this email because we received a password reset request for your account.') ->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', url('auth/password/reset', $this->token)) ->action('Reset Password', url('/auth/password/reset/' . $this->token . '?email=' . $notifiable->email))
->line('If you did not request a password reset, no further action is required.'); ->line('If you did not request a password reset, no further action is required.');
} }
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
} }

View file

@ -1,4 +1,26 @@
<?php <?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Notifications; namespace Pterodactyl\Notifications;

View file

@ -0,0 +1,150 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Observers;
use Auth;
use Cache;
use Carbon;
use Pterodactyl\Events;
use Pterodactyl\Models\Server;
use Pterodactyl\Jobs\DeleteServer;
use Pterodactyl\Jobs\SuspendServer;
use Pterodactyl\Notifications\ServerCreated;
use Illuminate\Foundation\Bus\DispatchesJobs;
class ServerObserver
{
use DispatchesJobs;
/**
* Listen to the Server creating event.
*
* @param Server $server [description]
* @return [type] [description]
*/
public function creating(Server $server)
{
event(new Events\Server\Creating($server));
}
/**
* Listen to the Server created event.
*
* @param Server $server [description]
* @return [type] [description]
*/
public function created(Server $server)
{
event(new Events\Server\Created($server));
// Queue Notification Email
$server->user->notify((new ServerCreated([
'name' => $server->name,
'memory' => $server->memory,
'node' => $server->node->name,
'service' => $server->service->name,
'option' => $server->option->name,
'uuidShort' => $server->uuidShort,
])));
}
/**
* Listen to the Server deleting event.
*
* @param Server $server [description]
* @return [type] [description]
*/
public function deleting(Server $server)
{
event(new Events\Server\Deleting($server));
$this->dispatch((new SuspendServer($server->id))->onQueue(env('QUEUE_HIGH', 'high')));
}
/**
* Listen to the Server deleted event.
*
* @param Server $server [description]
* @return [type] [description]
*/
public function deleted(Server $server)
{
event(new Events\Server\Deleted($server));
$this->dispatch(
(new DeleteServer($server->id))
->delay(Carbon::now()->addMinutes(env('APP_DELETE_MINUTES', 10)))
->onQueue(env('QUEUE_STANDARD', 'standard'))
);
}
/**
* Listen to the Server saving event.
*
* @param Server $server [description]
* @return [type] [description]
*/
public function saving(Server $server)
{
event(new Events\Server\Saving($server));
}
/**
* Listen to the Server saved event.
*
* @param Server $server [description]
* @return [type] [description]
*/
public function saved(Server $server)
{
event(new Events\Server\Saved($server));
}
/**
* Listen to the Server saving event.
*
* @param Server $server [description]
* @return [type] [description]
*/
public function updating(Server $server)
{
event(new Events\Server\Updating($server));
}
/**
* Listen to the Server saved event.
*
* @param Server $server [description]
* @return [type] [description]
*/
public function updated(Server $server)
{
// Clear Caches
Cache::forget('Server.byUuid.' . $server->uuid . Auth::user()->uuid);
Cache::forget('Server.byUuid.' . $server->uuidShort . Auth::user()->uuid);
event(new Events\Server\Updated($server));
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Observers;
use Pterodactyl\Events;
use Pterodactyl\Models\Subuser;
use Pterodactyl\Notifications\AddedToServer;
use Pterodactyl\Notifications\RemovedFromServer;
class SubuserObserver
{
/**
* Listen to the Subuser creating event.
*
* @param Subuser $subuser The eloquent Subuser model.
* @return void
*/
public function creating(Subuser $subuser)
{
event(new Events\Subuser\Creating($subuser));
}
/**
* Listen to the Subuser created event.
*
* @param Subuser $subuser The eloquent Subuser model.
* @return void
*/
public function created(Subuser $subuser)
{
event(new Events\Subuser\Created($subuser));
$subuser->user->notify((new AddedToServer([
'user' => $subuser->user->name_first,
'name' => $subuser->server->name,
'uuidShort' => $subuser->server->uuidShort,
])));
}
/**
* Listen to the Subuser deleting event.
*
* @param Subuser $subuser The eloquent Subuser model.
* @return void
*/
public function deleting(Subuser $subuser)
{
event(new Events\Subuser\Deleting($subuser));
}
/**
* Listen to the Subuser deleted event.
*
* @param Subuser $subuser The eloquent Subuser model.
* @return void
*/
public function deleted(Subuser $subuser)
{
event(new Events\Subuser\Deleted($subuser));
$subuser->user->notify((new RemovedFromServer([
'user' => $subuser->user->name_first,
'name' => $subuser->server->name,
])));
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Observers;
use DB;
use Pterodactyl\Events;
use Pterodactyl\Models\User;
use Pterodactyl\Notifications\AccountCreated;
class UserObserver
{
/**
* Listen to the User creating event.
*
* @param User $user The eloquent User model.
* @return void
*/
public function creating(User $user)
{
event(new Events\User\Creating($user));
}
/**
* Listen to the User created event.
*
* @param User $user The eloquent User model.
* @return void
*/
public function created(User $user)
{
event(new Events\User\Created($user));
$token = DB::table('password_resets')->where('email', $user->email)->orderBy('created_at', 'desc')->first();
$user->notify((new AccountCreated([
'name' => $user->name_first,
'username' => $user->username,
'token' => (! is_null($token)) ? $token->token : null,
])));
}
/**
* Listen to the User deleting event.
*
* @param User $user The eloquent User model.
* @return void
*/
public function deleting(User $user)
{
event(new Events\User\Deleting($user));
}
/**
* Listen to the User deleted event.
*
* @param User $user The eloquent User model.
* @return void
*/
public function deleted(User $user)
{
event(new Events\User\Deleted($user));
}
}

View file

@ -24,6 +24,8 @@
namespace Pterodactyl\Policies; namespace Pterodactyl\Policies;
use Cache;
use Carbon;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
@ -39,6 +41,29 @@ class ServerPolicy
// //
} }
/**
* Checks if the user has the given permission on/for the server.
*
* @param \Pterodactyl\Models\User $user
* @param \Pterodactyl\Models\Server $server
* @param $permission
* @return bool
*/
private function checkPermission(User $user, Server $server, $permission)
{
if ($this->isOwner($user, $server)) {
return true;
}
$permissions = Cache::remember('ServerPolicy.' . $user->uuid . $server->uuid, Carbon::now()->addSeconds(10), function () use ($user, $server) {
return $user->permissions()->server($server)->get()->transform(function ($item) {
return $item->permission;
})->values();
});
return $permissions->search($permission, true) !== false;
}
/** /**
* Determine if current user is the owner of a server. * Determine if current user is the owner of a server.
* *
@ -48,7 +73,7 @@ class ServerPolicy
*/ */
protected function isOwner(User $user, Server $server) protected function isOwner(User $user, Server $server)
{ {
return $server->owner === $user->id; return $server->owner_id === $user->id;
} }
/** /**
@ -521,21 +546,4 @@ class ServerPolicy
{ {
return $this->checkPermission($user, $server, 'set-allocation'); return $this->checkPermission($user, $server, 'set-allocation');
} }
/**
* Checks if the user has the given permission on/for the server.
*
* @param \Pterodactyl\Models\User $user
* @param \Pterodactyl\Models\Server $server
* @param $permission
* @return bool
*/
private function checkPermission(User $user, Server $server, $permission)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission($permission)->exists();
}
} }

View file

@ -1,7 +1,31 @@
<?php <?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Providers; namespace Pterodactyl\Providers;
use Pterodactyl\Models;
use Pterodactyl\Observers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
@ -13,7 +37,9 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot() public function boot()
{ {
// Models\User::observe(Observers\UserObserver::class);
Models\Server::observe(Observers\ServerObserver::class);
Models\Subuser::observe(Observers\SubuserObserver::class);
} }
/** /**

View file

@ -12,11 +12,7 @@ class EventServiceProvider extends ServiceProvider
* *
* @var array * @var array
*/ */
protected $listen = [ protected $listen = [];
'Pterodactyl\Events\ServerDeleted' => [
'Pterodactyl\Listeners\DeleteServerListener',
],
];
/** /**
* Register any other events for your application. * Register any other events for your application.

View file

@ -0,0 +1,61 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Providers;
use Pterodactyl\Extensions\PhraseAppTranslator;
use Illuminate\Translation\TranslationServiceProvider;
use Illuminate\Translation\Translator as IlluminateTranslator;
class PhraseAppTranslationProvider extends TranslationServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerLoader();
$this->app->singleton('translator', function ($app) {
$loader = $app['translation.loader'];
// When registering the translator component, we'll need to set the default
// locale as well as the fallback locale. So, we'll grab the application
// configuration so we can easily get both of these values from there.
$locale = $app['config']['app.locale'];
if ($app['config']['app.phrase_in_context']) {
$trans = new PhraseAppTranslator($loader, $locale);
} else {
$trans = new IlluminateTranslator($loader, $locale);
}
$trans->setFallback($app['config']['app.fallback_locale']);
return $trans;
});
}
}

View file

@ -62,8 +62,8 @@ class APIRepository
// Node Management Routes // Node Management Routes
'nodes.list', 'nodes.list',
'nodes.view',
'nodes.create', 'nodes.create',
'nodes.list',
'nodes.allocations', 'nodes.allocations',
'nodes.delete', 'nodes.delete',
@ -102,7 +102,7 @@ class APIRepository
{ {
$this->user = is_null($user) ? Auth::user() : $user; $this->user = is_null($user) ? Auth::user() : $user;
if (is_null($this->user)) { if (is_null($this->user)) {
throw new \Exception('Cannot access API Repository without passing a user to __construct().'); throw new \Exception('Cannot access API Repository without passing a user to constructor.');
} }
} }
@ -149,7 +149,7 @@ class APIRepository
$secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7); $secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7);
$key = new Models\APIKey; $key = new Models\APIKey;
$key->fill([ $key->fill([
'user' => $this->user->id, 'user_id' => $this->user->id,
'public' => str_random(16), 'public' => str_random(16),
'secret' => Crypt::encrypt($secretKey), 'secret' => Crypt::encrypt($secretKey),
'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed), 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed),
@ -178,7 +178,7 @@ class APIRepository
} }
} }
if ($this->user->root_admin === 1 && isset($data['adminPermissions'])) { if ($this->user->isRootAdmin() && isset($data['adminPermissions'])) {
foreach ($data['adminPermissions'] as $permNode) { foreach ($data['adminPermissions'] as $permNode) {
if (! strpos($permNode, ':')) { if (! strpos($permNode, ':')) {
continue; continue;
@ -224,8 +224,11 @@ class APIRepository
DB::beginTransaction(); DB::beginTransaction();
try { try {
$model = Models\APIKey::where('public', $key)->where('user', $this->user->id)->firstOrFail(); $model = Models\APIKey::with('permissions')->where('public', $key)->where('user_id', $this->user->id)->firstOrFail();
Models\APIPermission::where('key_id', $model->id)->delete(); foreach ($model->permissions as &$permission) {
$permission->delete();
}
$model->delete(); $model->delete();
DB::commit(); DB::commit();

View file

@ -24,7 +24,6 @@
namespace Pterodactyl\Repositories\Daemon; namespace Pterodactyl\Repositories\Daemon;
use GuzzleHttp\Client;
use Pterodactyl\Models; use Pterodactyl\Models;
use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
@ -32,14 +31,10 @@ use Pterodactyl\Exceptions\DisplayException;
class CommandRepository class CommandRepository
{ {
protected $server; protected $server;
protected $node;
protected $client;
public function __construct($server) public function __construct($server)
{ {
$this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server); $this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server);
$this->node = Models\Node::getByID($this->server->node);
$this->client = Models\Node::guzzleRequest($this->server->node);
} }
/** /**
@ -56,15 +51,10 @@ class CommandRepository
// Additionally not all calls to this will be from a logged in user. // Additionally not all calls to this will be from a logged in user.
// (e.g. task queue or API) // (e.g. task queue or API)
try { try {
$response = $this->client->request('POST', '/server/command', [ $response = $this->server->node->guzzleClient([
'headers' => [
'X-Access-Token' => $this->server->daemonSecret, 'X-Access-Token' => $this->server->daemonSecret,
'X-Access-Server' => $this->server->uuid, 'X-Access-Server' => $this->server->uuid,
], ])->request('POST', '/server/command', ['json' => ['command' => $command]]);
'json' => [
'command' => $command,
],
]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
throw new DisplayException('Command sending responded with a non-200 error code.'); throw new DisplayException('Command sending responded with a non-200 error code.');

View file

@ -26,7 +26,6 @@ namespace Pterodactyl\Repositories\Daemon;
use Exception; use Exception;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Repositories\HelperRepository; use Pterodactyl\Repositories\HelperRepository;
@ -40,28 +39,6 @@ class FileRepository
*/ */
protected $server; protected $server;
/**
* The Eloquent Model for the node corresponding with the requested server.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $node;
/**
* The Guzzle Client associated with the requested server and node.
*
* @var \GuzzleHttp\Client
*/
protected $client;
/**
* The Guzzle Client headers associated with the requested server and node.
* (non-administrative headers).
*
* @var array
*/
protected $headers;
/** /**
* Constructor. * Constructor.
* *
@ -69,10 +46,7 @@ class FileRepository
*/ */
public function __construct($uuid) public function __construct($uuid)
{ {
$this->server = Server::getByUUID($uuid); $this->server = Server::byUuid($uuid);
$this->node = Node::getByID($this->server->node);
$this->client = Node::guzzleRequest($this->server->node);
$this->headers = Server::getGuzzleHeaders($uuid);
} }
/** /**
@ -88,12 +62,9 @@ class FileRepository
} }
$file = (object) pathinfo($file); $file = (object) pathinfo($file);
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
$res = $this->client->request('GET', '/server/file/stat/' . rawurlencode($file->dirname . $file->basename), [ $res = $this->server->guzzleClient()->request('GET', '/server/file/stat/' . rawurlencode($file->dirname . $file->basename));
'headers' => $this->headers,
]);
$stat = json_decode($res->getBody()); $stat = json_decode($res->getBody());
if ($res->getStatusCode() !== 200 || ! isset($stat->size)) { if ($res->getStatusCode() !== 200 || ! isset($stat->size)) {
@ -108,9 +79,7 @@ class FileRepository
throw new DisplayException('That file is too large to open in the browser, consider using a SFTP client.'); throw new DisplayException('That file is too large to open in the browser, consider using a SFTP client.');
} }
$res = $this->client->request('GET', '/server/file/f/' . rawurlencode($file->dirname . $file->basename), [ $res = $this->server->guzzleClient()->request('GET', '/server/file/f/' . rawurlencode($file->dirname . $file->basename));
'headers' => $this->headers,
]);
$json = json_decode($res->getBody()); $json = json_decode($res->getBody());
if ($res->getStatusCode() !== 200 || ! isset($json->content)) { if ($res->getStatusCode() !== 200 || ! isset($json->content)) {
@ -137,11 +106,9 @@ class FileRepository
} }
$file = (object) pathinfo($file); $file = (object) pathinfo($file);
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
$res = $this->client->request('POST', '/server/file/save', [ $res = $this->server->guzzleClient()->request('POST', '/server/file/save', [
'headers' => $this->headers,
'json' => [ 'json' => [
'path' => rawurlencode($file->dirname . $file->basename), 'path' => rawurlencode($file->dirname . $file->basename),
'content' => $content, 'content' => $content,
@ -167,9 +134,7 @@ class FileRepository
throw new Exception('A valid directory must be specified in order to list its contents.'); throw new Exception('A valid directory must be specified in order to list its contents.');
} }
$res = $this->client->request('GET', '/server/directory/' . rawurlencode($directory), [ $res = $this->server->guzzleClient()->request('GET', '/server/directory/' . rawurlencode($directory));
'headers' => $this->headers,
]);
$json = json_decode($res->getBody()); $json = json_decode($res->getBody());
if ($res->getStatusCode() !== 200) { if ($res->getStatusCode() !== 200) {
@ -180,7 +145,7 @@ class FileRepository
$files = []; $files = [];
$folders = []; $folders = [];
foreach ($json as &$value) { foreach ($json as &$value) {
if ($value->directory === true) { if ($value->directory) {
// @TODO Handle Symlinks // @TODO Handle Symlinks
$folders[] = [ $folders[] = [
'entry' => $value->name, 'entry' => $value->name,
@ -189,7 +154,7 @@ class FileRepository
'date' => strtotime($value->modified), 'date' => strtotime($value->modified),
'mime' => $value->mime, 'mime' => $value->mime,
]; ];
} elseif ($value->file === true) { } elseif ($value->file) {
$files[] = [ $files[] = [
'entry' => $value->name, 'entry' => $value->name,
'directory' => trim($directory, '/'), 'directory' => trim($directory, '/'),

View file

@ -24,21 +24,16 @@
namespace Pterodactyl\Repositories\Daemon; namespace Pterodactyl\Repositories\Daemon;
use GuzzleHttp\Client;
use Pterodactyl\Models; use Pterodactyl\Models;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
class PowerRepository class PowerRepository
{ {
protected $server; protected $server;
protected $node;
protected $client;
public function __construct($server) public function __construct($server)
{ {
$this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server); $this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server);
$this->node = Models\Node::getByID($this->server->node);
$this->client = Models\Node::guzzleRequest($this->server->node);
} }
public function do($action) public function do($action)
@ -48,15 +43,10 @@ class PowerRepository
// Additionally not all calls to this will be from a logged in user. // Additionally not all calls to this will be from a logged in user.
// (e.g. task queue or API) // (e.g. task queue or API)
try { try {
$response = $this->client->request('PUT', '/server/power', [ $response = $this->server->node->guzzleClient([
'headers' => [
'X-Access-Token' => $this->server->daemonSecret, 'X-Access-Token' => $this->server->daemonSecret,
'X-Access-Server' => $this->server->uuid, 'X-Access-Server' => $this->server->uuid,
], ])->request('PUT', '/server/power', ['json' => ['action' => $action]]);
'json' => [
'action' => $action,
],
]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
throw new DisplayException('Power status responded with a non-200 error code.'); throw new DisplayException('Power status responded with a non-200 error code.');

View file

@ -114,28 +114,27 @@ class DatabaseRepository
/** /**
* Updates the password for a given database. * Updates the password for a given database.
* @param int $database The ID of the database to modify. * @param int $id The ID of the database to modify.
* @param string $password The new password to use for the database. * @param string $password The new password to use for the database.
* @return bool * @return bool
*/ */
public function modifyPassword($database, $password) public function modifyPassword($id, $password)
{ {
$db = Models\Database::findOrFail($database); $database = Models\Database::with('host')->findOrFail($id);
$dbr = Models\DatabaseServer::findOrFail($db->db_server);
DB::beginTransaction(); DB::beginTransaction();
try { try {
$db->password = Crypt::encrypt($password); $database->password = Crypt::encrypt($password);
$db->save(); $database->save();
$capsule = new Capsule; $capsule = new Capsule;
$capsule->addConnection([ $capsule->addConnection([
'driver' => 'mysql', 'driver' => 'mysql',
'host' => $dbr->host, 'host' => $database->host->host,
'port' => $dbr->port, 'port' => $database->host->port,
'database' => 'mysql', 'database' => 'mysql',
'username' => $dbr->username, 'username' => $database->host->username,
'password' => Crypt::decrypt($dbr->password), 'password' => Crypt::decrypt($database->host->password),
'charset' => 'utf8', 'charset' => 'utf8',
'collation' => 'utf8_unicode_ci', 'collation' => 'utf8_unicode_ci',
'prefix' => '', 'prefix' => '',
@ -147,8 +146,8 @@ class DatabaseRepository
$capsule->setAsGlobal(); $capsule->setAsGlobal();
Capsule::statement(sprintf( Capsule::statement(sprintf(
'SET PASSWORD FOR `%s`@`%s` = PASSWORD(\'%s\')', 'SET PASSWORD FOR `%s`@`%s` = PASSWORD(\'%s\')',
$db->username, $database->username,
$db->remote, $database->remote,
$password $password
)); ));
@ -161,13 +160,12 @@ class DatabaseRepository
/** /**
* Drops a database from the associated MySQL Server. * Drops a database from the associated MySQL Server.
* @param int $database The ID of the database to drop. * @param int $id The ID of the database to drop.
* @return bool * @return bool
*/ */
public function drop($database) public function drop($id)
{ {
$db = Models\Database::findOrFail($database); $database = Models\Database::with('host')->findOrFail($id);
$dbr = Models\DatabaseServer::findOrFail($db->db_server);
DB::beginTransaction(); DB::beginTransaction();
@ -175,11 +173,11 @@ class DatabaseRepository
$capsule = new Capsule; $capsule = new Capsule;
$capsule->addConnection([ $capsule->addConnection([
'driver' => 'mysql', 'driver' => 'mysql',
'host' => $dbr->host, 'host' => $database->host->host,
'port' => $dbr->port, 'port' => $database->host->port,
'database' => 'mysql', 'database' => 'mysql',
'username' => $dbr->username, 'username' => $database->host->username,
'password' => Crypt::decrypt($dbr->password), 'password' => Crypt::decrypt($database->host->password),
'charset' => 'utf8', 'charset' => 'utf8',
'collation' => 'utf8_unicode_ci', 'collation' => 'utf8_unicode_ci',
'prefix' => '', 'prefix' => '',
@ -190,10 +188,10 @@ class DatabaseRepository
$capsule->setAsGlobal(); $capsule->setAsGlobal();
Capsule::statement('DROP USER `' . $db->username . '`@`' . $db->remote . '`'); Capsule::statement('DROP USER `' . $database->username . '`@`' . $database->remote . '`');
Capsule::statement('DROP DATABASE `' . $db->database . '`'); Capsule::statement('DROP DATABASE `' . $database->database . '`');
$db->delete(); $database->delete();
DB::commit(); DB::commit();
@ -206,19 +204,19 @@ class DatabaseRepository
/** /**
* Deletes a database server from the system if it is empty. * Deletes a database server from the system if it is empty.
*
* @param int $server The ID of the Database Server. * @param int $server The ID of the Database Server.
* @return * @return
*/ */
public function delete($server) public function delete($server)
{ {
$dbh = Models\DatabaseServer::findOrFail($server); $host = Models\DatabaseServer::withCount('databases')->findOrFail($server);
$databases = Models\Database::where('db_server', $dbh->id)->count();
if ($databases > 0) { if ($host->databases_count > 0) {
throw new DisplayException('You cannot delete a database server that has active databases attached to it.'); throw new DisplayException('You cannot delete a database server that has active databases attached to it.');
} }
return $dbh->delete(); return $host->delete();
} }
/** /**
@ -268,8 +266,7 @@ class DatabaseRepository
// Allows us to check that we can connect to things. // Allows us to check that we can connect to things.
Capsule::select('SELECT 1 FROM dual'); Capsule::select('SELECT 1 FROM dual');
$dbh = new Models\DatabaseServer; Models\DatabaseServer::create([
$dbh->fill([
'name' => $data['name'], 'name' => $data['name'],
'host' => $data['host'], 'host' => $data['host'],
'port' => $data['port'], 'port' => $data['port'],
@ -278,7 +275,6 @@ class DatabaseRepository
'max_databases' => null, 'max_databases' => null,
'linked_node' => (! empty($data['linked_node']) && $data['linked_node'] > 0) ? $data['linked_node'] : null, 'linked_node' => (! empty($data['linked_node']) && $data['linked_node'] > 0) ? $data['linked_node'] : null,
]); ]);
$dbh->save();
DB::commit(); DB::commit();
} catch (\Exception $ex) { } catch (\Exception $ex) {

View file

@ -37,14 +37,15 @@ class LocationRepository
/** /**
* Creates a new location on the system. * Creates a new location on the system.
*
* @param array $data * @param array $data
* @throws Pterodactyl\Exceptions\DisplayValidationException * @throws \Pterodactyl\Exceptions\DisplayValidationException
* @return int * @return \Pterodactyl\Models\Location
*/ */
public function create(array $data) public function create(array $data)
{ {
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'short' => 'required|regex:/^[a-z0-9_.-]{1,10}$/i|unique:locations,short', 'short' => 'required|regex:/^[\w.-]{1,20}$/i|unique:locations,short',
'long' => 'required|string|min:1|max:255', 'long' => 'required|string|min:1|max:255',
]); ]);
@ -54,14 +55,12 @@ class LocationRepository
throw new DisplayValidationException($validator->errors()); throw new DisplayValidationException($validator->errors());
} }
$location = new Models\Location; $location = Models\Location::create([
$location->fill([
'long' => $data['long'], 'long' => $data['long'],
'short' => $data['short'], 'short' => $data['short'],
]); ]);
$location->save();
return $location->id; return $location;
} }
/** /**
@ -73,9 +72,11 @@ class LocationRepository
*/ */
public function edit($id, array $data) public function edit($id, array $data)
{ {
$location = Models\Location::findOrFail($id);
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'short' => 'regex:/^[a-z0-9_.-]{1,10}$/i', 'short' => 'required|regex:/^[\w.-]{1,20}$/i|unique:locations,short,' . $location->id,
'long' => 'string|min:1|max:255', 'long' => 'required|string|min:1|max:255',
]); ]);
// Run validator, throw catchable and displayable exception if it fails. // Run validator, throw catchable and displayable exception if it fails.
@ -84,15 +85,7 @@ class LocationRepository
throw new DisplayValidationException($validator->errors()); throw new DisplayValidationException($validator->errors());
} }
$location = Models\Location::findOrFail($id); $location->fill($data);
if (isset($data['short'])) {
$location->short = $data['short'];
}
if (isset($data['long'])) {
$location->long = $data['long'];
}
return $location->save(); return $location->save();
} }

View file

@ -44,7 +44,7 @@ class NodeRepository
// Validate Fields // Validate Fields
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'name' => 'required|regex:/^([\w .-]{1,100})$/', 'name' => 'required|regex:/^([\w .-]{1,100})$/',
'location' => 'required|numeric|min:1|exists:locations,id', 'location_id' => 'required|numeric|min:1|exists:locations,id',
'public' => 'required|numeric|between:0,1', 'public' => 'required|numeric|between:0,1',
'fqdn' => 'required|string|unique:nodes,fqdn', 'fqdn' => 'required|string|unique:nodes,fqdn',
'scheme' => 'required|regex:/^(http(s)?)$/', 'scheme' => 'required|regex:/^(http(s)?)$/',
@ -65,7 +65,7 @@ class NodeRepository
// Verify the FQDN if using SSL // Verify the FQDN if using SSL
if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') { if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') {
throw new DisplayException('A fully qualified domain name is required to use secure comunication on this node.'); throw new DisplayException('A fully qualified domain name is required to use a secure comunication method on this node.');
} }
// Verify FQDN is resolvable, or if not using SSL that the IP is valid. // Verify FQDN is resolvable, or if not using SSL that the IP is valid.
@ -81,12 +81,7 @@ class NodeRepository
$uuid = new UuidService; $uuid = new UuidService;
$data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret');
// Store the Data return Models\Node::create($data);
$node = new Models\Node;
$node->fill($data);
$node->save();
return $node->id;
} }
public function update($id, array $data) public function update($id, array $data)
@ -96,7 +91,7 @@ class NodeRepository
// Validate Fields // Validate Fields
$validator = $validator = Validator::make($data, [ $validator = $validator = Validator::make($data, [
'name' => 'regex:/^([\w .-]{1,100})$/', 'name' => 'regex:/^([\w .-]{1,100})$/',
'location' => 'numeric|min:1|exists:locations,id', 'location_id' => 'numeric|min:1|exists:locations,id',
'public' => 'numeric|between:0,1', 'public' => 'numeric|between:0,1',
'fqdn' => 'string|unique:nodes,fqdn,' . $id, 'fqdn' => 'string|unique:nodes,fqdn,' . $id,
'scheme' => 'regex:/^(http(s)?)$/', 'scheme' => 'regex:/^(http(s)?)$/',
@ -105,10 +100,10 @@ class NodeRepository
'disk' => 'numeric|min:1', 'disk' => 'numeric|min:1',
'disk_overallocate' => 'numeric|min:-1', 'disk_overallocate' => 'numeric|min:-1',
'upload_size' => 'numeric|min:0', 'upload_size' => 'numeric|min:0',
'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/', 'daemonBase' => 'sometimes|regex:/^([\/][\d\w.\-\/]+)$/',
'daemonSFTP' => 'numeric|between:1,65535', 'daemonSFTP' => 'numeric|between:1,65535',
'daemonListen' => 'numeric|between:1,65535', 'daemonListen' => 'numeric|between:1,65535',
'reset_secret' => 'sometimes|accepted', 'reset_secret' => 'sometimes|nullable|accepted',
]); ]);
// Run validator, throw catchable and displayable exception if it fails. // Run validator, throw catchable and displayable exception if it fails.
@ -143,7 +138,7 @@ class NodeRepository
} }
// Set the Secret // Set the Secret
if (isset($data['reset_secret'])) { if (isset($data['reset_secret']) && ! is_null($data['reset_secret'])) {
$uuid = new UuidService; $uuid = new UuidService;
$data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret');
unset($data['reset_secret']); unset($data['reset_secret']);
@ -152,18 +147,12 @@ class NodeRepository
$oldDaemonKey = $node->daemonSecret; $oldDaemonKey = $node->daemonSecret;
$node->update($data); $node->update($data);
try { try {
$client = Models\Node::guzzleRequest($node->id); $node->guzzleClient(['X-Access-Token' => $oldDaemonKey])->request('PATCH', '/config', [
$client->request('PATCH', '/config', [
'headers' => [
'X-Access-Token' => $oldDaemonKey,
],
'json' => [ 'json' => [
'web' => [ 'web' => [
'listen' => $node->daemonListen, 'listen' => $node->daemonListen,
'ssl' => [ 'ssl' => [
'enabled' => ($node->scheme === 'https'), 'enabled' => ($node->scheme === 'https'),
'certificate' => '/etc/letsencrypt/live/' . $node->fqdn . '/fullchain.pem',
'key' => '/etc/letsencrypt/live/' . $node->fqdn . '/privkey.pem',
], ],
], ],
'sftp' => [ 'sftp' => [
@ -221,34 +210,34 @@ class NodeRepository
foreach ($portBlock as $assignPort) { foreach ($portBlock as $assignPort) {
$alloc = Models\Allocation::firstOrNew([ $alloc = Models\Allocation::firstOrNew([
'node' => $node->id, 'node_id' => $node->id,
'ip' => $ip, 'ip' => $ip,
'port' => $assignPort, 'port' => $assignPort,
]); ]);
if (! $alloc->exists) { if (! $alloc->exists) {
$alloc->fill([ $alloc->fill([
'node' => $node->id, 'node_id' => $node->id,
'ip' => $ip, 'ip' => $ip,
'port' => $assignPort, 'port' => $assignPort,
'ip_alias' => $setAlias, 'ip_alias' => $setAlias,
'assigned_to' => null, 'server_id' => null,
]); ]);
$alloc->save(); $alloc->save();
} }
} }
} else { } else {
$alloc = Models\Allocation::firstOrNew([ $alloc = Models\Allocation::firstOrNew([
'node' => $node->id, 'node_id' => $node->id,
'ip' => $ip, 'ip' => $ip,
'port' => $port, 'port' => $port,
]); ]);
if (! $alloc->exists) { if (! $alloc->exists) {
$alloc->fill([ $alloc->fill([
'node' => $node->id, 'node_id' => $node->id,
'ip' => $ip, 'ip' => $ip,
'port' => $port, 'port' => $port,
'ip_alias' => $setAlias, 'ip_alias' => $setAlias,
'assigned_to' => null, 'server_id' => null,
]); ]);
$alloc->save(); $alloc->save();
} }
@ -266,8 +255,8 @@ class NodeRepository
public function delete($id) public function delete($id)
{ {
$node = Models\Node::findOrFail($id); $node = Models\Node::withCount('servers')->findOrFail($id);
if (Models\Server::where('node', $id)->count() > 0) { if ($node->servers_count > 0) {
throw new DisplayException('You cannot delete a node with servers currently attached to it.'); throw new DisplayException('You cannot delete a node with servers currently attached to it.');
} }
@ -280,7 +269,7 @@ class NodeRepository
]); ]);
// Delete Allocations // Delete Allocations
Models\Allocation::where('node', $node->id)->delete(); Models\Allocation::where('node_id', $node->id)->delete();
// Delete configure tokens // Delete configure tokens
Models\NodeConfigurationToken::where('node', $node->id)->delete(); Models\NodeConfigurationToken::where('node', $node->id)->delete();

View file

@ -29,11 +29,10 @@ use Log;
use Crypt; use Crypt;
use Validator; use Validator;
use Pterodactyl\Models; use Pterodactyl\Models;
use Pterodactyl\Events\ServerDeleted;
use Pterodactyl\Services\UuidService; use Pterodactyl\Services\UuidService;
use GuzzleHttp\Exception\TransferException;
use Pterodactyl\Services\DeploymentService; use Pterodactyl\Services\DeploymentService;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Notifications\ServerCreated;
use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Exceptions\DisplayValidationException;
class ServerRepository class ServerRepository
@ -91,16 +90,17 @@ class ServerRepository
'io' => 'required|numeric|min:10|max:1000', 'io' => 'required|numeric|min:10|max:1000',
'cpu' => 'required|numeric|min:0', 'cpu' => 'required|numeric|min:0',
'disk' => 'required|numeric|min:0', 'disk' => 'required|numeric|min:0',
'service' => 'required|numeric|min:1|exists:services,id', 'service_id' => 'required|numeric|min:1|exists:services,id',
'option' => 'required|numeric|min:1|exists:service_options,id', 'option_id' => 'required|numeric|min:1|exists:service_options,id',
'pack' => 'sometimes|nullable|numeric|min:0', 'location_id' => 'required|numeric|min:1|exists:locations,id',
'pack_id' => 'sometimes|nullable|numeric|min:0',
'startup' => 'string', 'startup' => 'string',
'custom_image_name' => 'required_if:use_custom_image,on', 'custom_image_name' => 'required_if:use_custom_image,on',
'auto_deploy' => 'sometimes|boolean', 'auto_deploy' => 'sometimes|boolean',
'custom_id' => 'sometimes|required|numeric|unique:servers,id', 'custom_id' => 'sometimes|required|numeric|unique:servers,id',
]); ]);
$validator->sometimes('node', 'bail|required|numeric|min:1|exists:nodes,id', function ($input) { $validator->sometimes('node_id', 'bail|required|numeric|min:1|exists:nodes,id', function ($input) {
return ! ($input->auto_deploy); return ! ($input->auto_deploy);
}); });
@ -112,7 +112,7 @@ class ServerRepository
return ! $input->auto_deploy && ! $input->allocation; return ! $input->auto_deploy && ! $input->allocation;
}); });
$validator->sometimes('allocation', 'numeric|exists:allocations,id', function ($input) { $validator->sometimes('allocation_id', 'numeric|exists:allocations,id', function ($input) {
return ! ($input->auto_deploy || ($input->port && $input->ip)); return ! ($input->auto_deploy || ($input->port && $input->ip));
}); });
@ -122,12 +122,7 @@ class ServerRepository
throw new DisplayValidationException($validator->errors()); throw new DisplayValidationException($validator->errors());
} }
if (is_int($data['owner'])) { $user = Models\User::select('id', 'email')->where((is_int($data['owner'])) ? 'id' : 'email', $data['owner'])->first();
$user = Models\User::select('id', 'email')->where('id', $data['owner'])->first();
} else {
$user = Models\User::select('id', 'email')->where('email', $data['owner'])->first();
}
if (! $user) { if (! $user) {
throw new DisplayException('The user id or email passed to the function was not found on the system.'); throw new DisplayException('The user id or email passed to the function was not found on the system.');
} }
@ -136,23 +131,24 @@ class ServerRepository
if (isset($data['auto_deploy']) && in_array($data['auto_deploy'], [true, 1, '1'])) { if (isset($data['auto_deploy']) && in_array($data['auto_deploy'], [true, 1, '1'])) {
// This is an auto-deployment situation // This is an auto-deployment situation
// Ignore any other passed node data // Ignore any other passed node data
unset($data['node'], $data['ip'], $data['port'], $data['allocation']); unset($data['node_id'], $data['ip'], $data['port'], $data['allocation_id']);
$autoDeployed = true; $autoDeployed = true;
$node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location']); $node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location_id']);
$allocation = DeploymentService::randomAllocation($node->id); $allocation = DeploymentService::randomAllocation($node->id);
} else { } else {
$node = Models\Node::getByID($data['node']); $node = Models\Node::findOrFail($data['node_id']);
} }
// Verify IP & Port are a.) free and b.) assigned to the node. // Verify IP & Port are a.) free and b.) assigned to the node.
// We know the node exists because of 'exists:nodes,id' in the validation // We know the node exists because of 'exists:nodes,id' in the validation
if (! $autoDeployed) { if (! $autoDeployed) {
if (! isset($data['allocation'])) { if (! isset($data['allocation_id'])) {
$allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->where('node', $data['node'])->whereNull('assigned_to')->first(); $model = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port']);
} else { } else {
$allocation = Models\Allocation::where('id', $data['allocation'])->where('node', $data['node'])->whereNull('assigned_to')->first(); $model = Models\Allocation::where('id', $data['allocation_id']);
} }
$allocation = $model->where('node_id', $data['node_id'])->whereNull('server_id')->first();
} }
// Something failed in the query, either that combo doesn't exist, or it is in use. // Something failed in the query, either that combo doesn't exist, or it is in use.
@ -164,28 +160,28 @@ class ServerRepository
// We know the service and option exists because of the validation. // We know the service and option exists because of the validation.
// We need to verify that the option exists for the service, and then check for // We need to verify that the option exists for the service, and then check for
// any required variable fields. (fields are labeled env_<env_variable>) // any required variable fields. (fields are labeled env_<env_variable>)
$option = Models\ServiceOptions::where('id', $data['option'])->where('parent_service', $data['service'])->first(); $option = Models\ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first();
if (! $option) { if (! $option) {
throw new DisplayException('The requested service option does not exist for the specified service.'); throw new DisplayException('The requested service option does not exist for the specified service.');
} }
// Validate the Pack // Validate the Pack
if ($data['pack'] == 0) { if ($data['pack_id'] == 0) {
$data['pack'] = null; $data['pack_id'] = null;
} }
if (! is_null($data['pack'])) { if (! is_null($data['pack_id'])) {
$pack = Models\ServicePack::where('id', $data['pack'])->where('option', $data['option'])->first(); $pack = Models\ServicePack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first();
if (! $pack) { if (! $pack) {
throw new DisplayException('The requested service pack does not seem to exist for this combination.'); throw new DisplayException('The requested service pack does not seem to exist for this combination.');
} }
} }
// Load up the Service Information // Load up the Service Information
$service = Models\Service::find($option->parent_service); $service = Models\Service::find($option->service_id);
// Check those Variables // Check those Variables
$variables = Models\ServiceVariables::where('option_id', $data['option'])->get(); $variables = Models\ServiceVariable::where('option_id', $data['option_id'])->get();
$variableList = []; $variableList = [];
if ($variables) { if ($variables) {
foreach ($variables as $variable) { foreach ($variables as $variable) {
@ -220,7 +216,7 @@ class ServerRepository
// Check Overallocation // Check Overallocation
if (! $autoDeployed) { if (! $autoDeployed) {
if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) { if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) {
$totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(); $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first();
// Check memory limits // Check memory limits
if (is_numeric($node->memory_overallocate)) { if (is_numeric($node->memory_overallocate)) {
@ -259,20 +255,20 @@ class ServerRepository
$server->fill([ $server->fill([
'uuid' => $genUuid, 'uuid' => $genUuid,
'uuidShort' => $genShortUuid, 'uuidShort' => $genShortUuid,
'node' => $node->id, 'node_id' => $node->id,
'name' => $data['name'], 'name' => $data['name'],
'suspended' => 0, 'suspended' => 0,
'owner' => $user->id, 'owner_id' => $user->id,
'memory' => $data['memory'], 'memory' => $data['memory'],
'swap' => $data['swap'], 'swap' => $data['swap'],
'disk' => $data['disk'], 'disk' => $data['disk'],
'io' => $data['io'], 'io' => $data['io'],
'cpu' => $data['cpu'], 'cpu' => $data['cpu'],
'oom_disabled' => (isset($data['oom_disabled'])) ? true : false, 'oom_disabled' => (isset($data['oom_disabled'])) ? true : false,
'allocation' => $allocation->id, 'allocation_id' => $allocation->id,
'service' => $data['service'], 'service_id' => $data['service_id'],
'option' => $data['option'], 'option_id' => $data['option_id'],
'pack' => $data['pack'], 'pack_id' => $data['pack_id'],
'startup' => $data['startup'], 'startup' => $data['startup'],
'daemonSecret' => $uuid->generate('servers', 'daemonSecret'), 'daemonSecret' => $uuid->generate('servers', 'daemonSecret'),
'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image, 'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image,
@ -282,7 +278,7 @@ class ServerRepository
$server->save(); $server->save();
// Mark Allocation in Use // Mark Allocation in Use
$allocation->assigned_to = $server->id; $allocation->server_id = $server->id;
$allocation->save(); $allocation->save();
// Add Variables // Add Variables
@ -293,28 +289,14 @@ class ServerRepository
foreach ($variableList as $item) { foreach ($variableList as $item) {
$environmentVariables[$item['env']] = $item['val']; $environmentVariables[$item['env']] = $item['val'];
Models\ServerVariables::create([ Models\ServerVariable::create([
'server_id' => $server->id, 'server_id' => $server->id,
'variable_id' => $item['id'], 'variable_id' => $item['id'],
'variable_value' => $item['val'], 'variable_value' => $item['val'],
]); ]);
} }
// Queue Notification Email $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [
$user->notify((new ServerCreated([
'name' => $server->name,
'memory' => $server->memory,
'node' => $node->name,
'service' => $service->name,
'option' => $option->name,
'uuidShort' => $server->uuidShort,
])));
$client = Models\Node::guzzleRequest($node->id);
$client->request('POST', '/servers', [
'headers' => [
'X-Access-Token' => $node->daemonSecret,
],
'json' => [ 'json' => [
'uuid' => (string) $server->uuid, 'uuid' => (string) $server->uuid,
'user' => $server->username, 'user' => $server->username,
@ -348,8 +330,8 @@ class ServerRepository
DB::commit(); DB::commit();
return $server->id; return $server;
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (TransferException $ex) {
DB::rollBack(); DB::rollBack();
throw new DisplayException('There was an error while attempting to connect to the daemon to add this server.', $ex); throw new DisplayException('There was an error while attempting to connect to the daemon to add this server.', $ex);
} catch (\Exception $ex) { } catch (\Exception $ex) {
@ -384,20 +366,19 @@ class ServerRepository
DB::beginTransaction(); DB::beginTransaction();
try { try {
$server = Models\Server::findOrFail($id); $server = Models\Server::with('user')->findOrFail($id);
$owner = Models\User::findOrFail($server->owner);
// Update daemon secret if it was passed. // Update daemon secret if it was passed.
if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $owner->email)) { if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $server->user->email)) {
$oldDaemonKey = $server->daemonSecret; $oldDaemonKey = $server->daemonSecret;
$server->daemonSecret = $uuid->generate('servers', 'daemonSecret'); $server->daemonSecret = $uuid->generate('servers', 'daemonSecret');
$resetDaemonKey = true; $resetDaemonKey = true;
} }
// Update Server Owner if it was passed. // Update Server Owner if it was passed.
if (isset($data['owner']) && $data['owner'] !== $owner->email) { if (isset($data['owner']) && $data['owner'] !== $server->user->email) {
$newOwner = Models\User::select('id')->where('email', $data['owner'])->first(); $newOwner = Models\User::select('id')->where('email', $data['owner'])->first();
$server->owner = $newOwner->id; $server->owner_id = $newOwner->id;
} }
// Update Server Name if it was passed. // Update Server Name if it was passed.
@ -415,15 +396,10 @@ class ServerRepository
return true; return true;
} }
// If we need to update do it here. $res = $server->node->guzzleClient([
$node = Models\Node::getByID($server->node);
$client = Models\Node::guzzleRequest($server->node);
$res = $client->request('PATCH', '/server', [
'headers' => [
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
'X-Access-Token' => $node->daemonSecret, 'X-Access-Token' => $server->node->daemonSecret,
], ])->request('PATCH', '/server', [
'exceptions' => false, 'exceptions' => false,
'json' => [ 'json' => [
'keys' => [ 'keys' => [
@ -472,14 +448,10 @@ class ServerRepository
$server->image = $data['image']; $server->image = $data['image'];
$server->save(); $server->save();
$node = Models\Node::getByID($server->node); $server->node->guzzleClient([
$client = Models\Node::guzzleRequest($server->node);
$client->request('PATCH', '/server', [
'headers' => [
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
'X-Access-Token' => $node->daemonSecret, 'X-Access-Token' => $server->node->daemonSecret,
], ])->request('PATCH', '/server', [
'json' => [ 'json' => [
'build' => [ 'build' => [
'image' => $server->image, 'image' => $server->image,
@ -490,7 +462,7 @@ class ServerRepository
DB::commit(); DB::commit();
return true; return true;
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (TransferException $ex) {
DB::rollBack(); DB::rollBack();
throw new DisplayException('An error occured while attempting to update the container image.', $ex); throw new DisplayException('An error occured while attempting to update the container image.', $ex);
} catch (\Exception $ex) { } catch (\Exception $ex) {
@ -530,27 +502,25 @@ class ServerRepository
DB::beginTransaction(); DB::beginTransaction();
try { try {
$server = Models\Server::findOrFail($id); $server = Models\Server::with('allocation', 'allocations')->findOrFail($id);
$allocation = Models\Allocation::findOrFail($server->allocation);
$newBuild = []; $newBuild = [];
if (isset($data['default'])) { if (isset($data['default'])) {
list($ip, $port) = explode(':', $data['default']); list($ip, $port) = explode(':', $data['default']);
if ($ip !== $allocation->ip || (int) $port !== $allocation->port) { if ($ip !== $server->allocation->ip || (int) $port !== $server->allocation->port) {
$selection = Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->first(); $selection = $server->allocations->where('ip', $ip)->where('port', $port)->first();
if (! $selection) { if (! $selection) {
throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.'); throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.');
} }
$server->allocation = $selection->id; $server->allocation_id = $selection->id;
$newBuild['default'] = [ $newBuild['default'] = [
'ip' => $ip, 'ip' => $ip,
'port' => (int) $port, 'port' => (int) $port,
]; ];
// Re-Run to keep updated for rest of function // Re-Run to keep updated for rest of function
$allocation = Models\Allocation::findOrFail($server->allocation); $server->load('allocation');
} }
} }
@ -565,15 +535,17 @@ class ServerRepository
} }
// Can't remove the assigned IP/Port combo // Can't remove the assigned IP/Port combo
if ($ip === $allocation->ip && (int) $port === (int) $allocation->port) { if ($ip === $server->allocation->ip && (int) $port === (int) $server->allocation->port) {
break; break;
} }
$newPorts = true; $newPorts = true;
Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->update([ $server->allocations->where('ip', $ip)->where('port', $port)->update([
'assigned_to' => null, 'server_id' => null,
]); ]);
} }
$server->load('allocations');
} }
// Add Assignments // Add Assignments
@ -586,21 +558,22 @@ class ServerRepository
} }
// Don't allow double port assignments // Don't allow double port assignments
if (Models\Allocation::where('port', $port)->where('assigned_to', $server->id)->count() !== 0) { if ($server->allocations->where('port', $port)->count() !== 0) {
break; break;
} }
$newPorts = true; $newPorts = true;
Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('assigned_to')->update([ Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('server_id')->update([
'assigned_to' => $server->id, 'server_id' => $server->id,
]); ]);
} }
$server->load('allocations');
} }
// Loop All Assignments // Loop All Assignments
$additionalAssignments = []; $additionalAssignments = [];
$assignments = Models\Allocation::where('assigned_to', $server->id)->get(); foreach ($server->allocations as &$assignment) {
foreach ($assignments as &$assignment) {
if (array_key_exists((string) $assignment->ip, $additionalAssignments)) { if (array_key_exists((string) $assignment->ip, $additionalAssignments)) {
array_push($additionalAssignments[(string) $assignment->ip], (int) $assignment->port); array_push($additionalAssignments[(string) $assignment->ip], (int) $assignment->port);
} else { } else {
@ -646,14 +619,10 @@ class ServerRepository
$server->save(); $server->save();
if (! empty($newBuild)) { if (! empty($newBuild)) {
$node = Models\Node::getByID($server->node); $server->node->guzzleClient([
$client = Models\Node::guzzleRequest($server->node);
$client->request('PATCH', '/server', [
'headers' => [
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
'X-Access-Token' => $node->daemonSecret, 'X-Access-Token' => $server->node->daemonSecret,
], ])->request('PATCH', '/server', [
'json' => [ 'json' => [
'build' => $newBuild, 'build' => $newBuild,
], ],
@ -663,7 +632,7 @@ class ServerRepository
DB::commit(); DB::commit();
return true; return true;
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (TransferException $ex) {
DB::rollBack(); DB::rollBack();
throw new DisplayException('An error occured while attempting to update the configuration.', $ex); throw new DisplayException('An error occured while attempting to update the configuration.', $ex);
} catch (\Exception $ex) { } catch (\Exception $ex) {
@ -674,34 +643,34 @@ class ServerRepository
public function updateStartup($id, array $data, $admin = false) public function updateStartup($id, array $data, $admin = false)
{ {
$server = Models\Server::findOrFail($id); $server = Models\Server::with('variables', 'option.variables')->findOrFail($id);
DB::beginTransaction(); DB::beginTransaction();
try { try {
// Check the startup // Check the startup
if (isset($data['startup'])) { if (isset($data['startup']) && $admin) {
$server->startup = $data['startup']; $server->startup = $data['startup'];
$server->save(); $server->save();
} }
// Check those Variables // Check those Variables
$variables = Models\ServiceVariables::select( $server->option->variables->transform(function ($item, $key) use ($server) {
'service_variables.*', $displayValue = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first();
DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_currentValue') $item->server_value = (! is_null($displayValue)) ? $displayValue : $item->default_value;
)->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id')
->where('option_id', $server->option) return $item;
->get(); });
$variableList = []; $variableList = [];
if ($variables) { if ($server->option->variables) {
foreach ($variables as &$variable) { foreach ($server->option->variables as &$variable) {
// Move on if the new data wasn't even sent // Move on if the new data wasn't even sent
if (! isset($data[$variable->env_variable])) { if (! isset($data[$variable->env_variable])) {
$variableList[] = [ $variableList[] = [
'id' => $variable->id, 'id' => $variable->id,
'env' => $variable->env_variable, 'env' => $variable->env_variable,
'val' => $variable->a_currentValue, 'val' => $variable->server_value,
]; ];
continue; continue;
} }
@ -719,13 +688,13 @@ class ServerRepository
// Is the variable required? // Is the variable required?
// @TODO: is this even logical to perform this check? // @TODO: is this even logical to perform this check?
if (isset($data[$variable->env_variable]) && empty($data[$variable->env_variable])) { if (isset($data[$variable->env_variable]) && empty($data[$variable->env_variable])) {
if ($variable->required === 1) { if ($variable->required) {
throw new DisplayException('A required service option variable field (' . $variable->env_variable . ') was included in this request but was left blank.'); throw new DisplayException('A required service option variable field (' . $variable->env_variable . ') was included in this request but was left blank.');
} }
} }
// Variable hidden and/or not user editable // Variable hidden and/or not user editable
if (($variable->user_viewable === 0 || $variable->user_editable === 0) && ! $admin) { if ((! $variable->user_viewable || ! $variable->user_editable) && ! $admin) {
throw new DisplayException('A service option variable field (' . $variable->env_variable . ') does not exist or you do not have permission to edit it.'); throw new DisplayException('A service option variable field (' . $variable->env_variable . ') does not exist or you do not have permission to edit it.');
} }
@ -750,7 +719,7 @@ class ServerRepository
$environmentVariables[$item['env']] = $item['val']; $environmentVariables[$item['env']] = $item['val'];
// Update model or make a new record if it doesn't exist. // Update model or make a new record if it doesn't exist.
$model = Models\ServerVariables::firstOrNew([ $model = Models\ServerVariable::firstOrNew([
'variable_id' => $item['id'], 'variable_id' => $item['id'],
'server_id' => $server->id, 'server_id' => $server->id,
]); ]);
@ -758,14 +727,10 @@ class ServerRepository
$model->save(); $model->save();
} }
$node = Models\Node::getByID($server->node); $server->node->guzzleClient([
$client = Models\Node::guzzleRequest($server->node);
$client->request('PATCH', '/server', [
'headers' => [
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
'X-Access-Token' => $node->daemonSecret, 'X-Access-Token' => $server->node->daemonSecret,
], ])->request('PATCH', '/server', [
'json' => [ 'json' => [
'build' => [ 'build' => [
'env|overwrite' => $environmentVariables, 'env|overwrite' => $environmentVariables,
@ -776,7 +741,7 @@ class ServerRepository
DB::commit(); DB::commit();
return true; return true;
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (TransferException $ex) {
DB::rollBack(); DB::rollBack();
throw new DisplayException('An error occured while attempting to update the server configuration.', $ex); throw new DisplayException('An error occured while attempting to update the server configuration.', $ex);
} catch (\Exception $ex) { } catch (\Exception $ex) {
@ -791,15 +756,14 @@ class ServerRepository
DB::beginTransaction(); DB::beginTransaction();
try { try {
if ($force === 'force' || $force === true) { if ($force === 'force' || $force) {
$server->installed = 3; $server->installed = 3;
$server->save(); $server->save();
} }
$server->delete(); $server->delete();
DB::commit();
event(new ServerDeleted($server->id)); return DB::commit();
} catch (\Exception $ex) { } catch (\Exception $ex) {
DB::rollBack(); DB::rollBack();
throw $ex; throw $ex;
@ -808,8 +772,7 @@ class ServerRepository
public function deleteNow($id, $force = false) public function deleteNow($id, $force = false)
{ {
$server = Models\Server::withTrashed()->findOrFail($id); $server = Models\Server::withTrashed()->with('node')->findOrFail($id);
$node = Models\Node::findOrFail($server->node);
// Handle server being restored previously or // Handle server being restored previously or
// an accidental queue. // an accidental queue.
@ -820,18 +783,20 @@ class ServerRepository
DB::beginTransaction(); DB::beginTransaction();
try { try {
// Unassign Allocations // Unassign Allocations
Models\Allocation::where('assigned_to', $server->id)->update([ Models\Allocation::where('server_id', $server->id)->update([
'assigned_to' => null, 'server_id' => null,
]); ]);
// Remove Variables // Remove Variables
Models\ServerVariables::where('server_id', $server->id)->delete(); Models\ServerVariable::where('server_id', $server->id)->delete();
// Remove Permissions (Foreign Key requires before Subusers)
Models\Permission::where('server_id', $server->id)->delete();
// Remove SubUsers // Remove SubUsers
Models\Subuser::where('server_id', $server->id)->delete(); foreach (Models\Subuser::with('permissions')->where('server_id', $server->id)->get() as &$subuser) {
foreach ($subuser->permissions as &$permission) {
$permission->delete();
}
$subuser->delete();
}
// Remove Downloads // Remove Downloads
Models\Download::where('server', $server->uuid)->delete(); Models\Download::where('server', $server->uuid)->delete();
@ -847,17 +812,14 @@ class ServerRepository
$repository->drop($database->id); $repository->drop($database->id);
} }
$client = Models\Node::guzzleRequest($server->node); $server->node->guzzleClient([
$client->request('DELETE', '/servers', [ 'X-Access-Token' => $server->node->daemonSecret,
'headers' => [
'X-Access-Token' => $node->daemonSecret,
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
], ])->request('DELETE', '/servers');
]);
$server->forceDelete(); $server->forceDelete();
DB::commit(); DB::commit();
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (TransferException $ex) {
// Set installed is set to 3 when force deleting. // Set installed is set to 3 when force deleting.
if ($server->installed === 3 || $force) { if ($server->installed === 3 || $force) {
$server->forceDelete(); $server->forceDelete();
@ -887,7 +849,7 @@ class ServerRepository
if ($server->installed === 2) { if ($server->installed === 2) {
throw new DisplayException('This server was marked as having a failed install, you cannot override this.'); throw new DisplayException('This server was marked as having a failed install, you cannot override this.');
} }
$server->installed = ($server->installed === 1) ? 0 : 1; $server->installed = ! $server->installed;
return $server->save(); return $server->save();
} }
@ -899,31 +861,27 @@ class ServerRepository
*/ */
public function suspend($id, $deleted = false) public function suspend($id, $deleted = false)
{ {
$server = ($deleted) ? Models\Server::withTrashed()->findOrFail($id) : Models\Server::findOrFail($id); $server = Models\Server::withTrashed()->with('node')->findOrFail($id);
$node = Models\Node::findOrFail($server->node);
DB::beginTransaction(); DB::beginTransaction();
try { try {
// Already suspended, no need to make more requests. // Already suspended, no need to make more requests.
if ($server->suspended === 1) { if ($server->suspended) {
return true; return true;
} }
$server->suspended = 1; $server->suspended = 1;
$server->save(); $server->save();
$client = Models\Node::guzzleRequest($server->node); $server->node->guzzleClient([
$client->request('POST', '/server/suspend', [ 'X-Access-Token' => $server->node->daemonSecret,
'headers' => [
'X-Access-Token' => $node->daemonSecret,
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
], ])->request('POST', '/server/suspend');
]);
return DB::commit(); return DB::commit();
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (TransferException $ex) {
DB::rollBack(); DB::rollBack();
throw new DisplayException('An error occured while attempting to contact the remote daemon to suspend this server.', $ex); throw new DisplayException('An error occured while attempting to contact the remote daemon to suspend this server.', $ex);
} catch (\Exception $ex) { } catch (\Exception $ex) {
@ -939,8 +897,7 @@ class ServerRepository
*/ */
public function unsuspend($id) public function unsuspend($id)
{ {
$server = Models\Server::findOrFail($id); $server = Models\Server::with('node')->findOrFail($id);
$node = Models\Node::findOrFail($server->node);
DB::beginTransaction(); DB::beginTransaction();
@ -954,16 +911,13 @@ class ServerRepository
$server->suspended = 0; $server->suspended = 0;
$server->save(); $server->save();
$client = Models\Node::guzzleRequest($server->node); $server->node->guzzleClient([
$client->request('POST', '/server/unsuspend', [ 'X-Access-Token' => $server->node->daemonSecret,
'headers' => [
'X-Access-Token' => $node->daemonSecret,
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
], ])->request('POST', '/server/unsuspend');
]);
return DB::commit(); return DB::commit();
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (TransferException $ex) {
DB::rollBack(); DB::rollBack();
throw new DisplayException('An error occured while attempting to contact the remote daemon to un-suspend this server.', $ex); throw new DisplayException('An error occured while attempting to contact the remote daemon to un-suspend this server.', $ex);
} catch (\Exception $ex) { } catch (\Exception $ex) {
@ -974,12 +928,9 @@ class ServerRepository
public function updateSFTPPassword($id, $password) public function updateSFTPPassword($id, $password)
{ {
$server = Models\Server::findOrFail($id); $server = Models\Server::with('node')->findOrFail($id);
$node = Models\Node::findOrFail($server->node);
$validator = Validator::make([ $validator = Validator::make(['password' => $password], [
'password' => $password,
], [
'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/', 'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/',
]); ]);
@ -993,21 +944,17 @@ class ServerRepository
try { try {
$server->save(); $server->save();
$client = Models\Node::guzzleRequest($server->node); $server->node->guzzleClient([
$client->request('POST', '/server/password', [ 'X-Access-Token' => $server->node->daemonSecret,
'headers' => [
'X-Access-Token' => $node->daemonSecret,
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
], ])->request('POST', '/server/password', [
'json' => [ 'json' => ['password' => $password],
'password' => $password,
],
]); ]);
DB::commit(); DB::commit();
return true; return true;
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (TransferException $ex) {
DB::rollBack(); DB::rollBack();
throw new DisplayException('There was an error while attmping to contact the remote service to change the password.', $ex); throw new DisplayException('There was an error while attmping to contact the remote service to change the password.', $ex);
} catch (\Exception $ex) { } catch (\Exception $ex) {

View file

@ -62,8 +62,8 @@ class Option
$data['startup'] = null; $data['startup'] = null;
} }
$option = new Models\ServiceOptions; $option = new Models\ServiceOption;
$option->parent_service = $service->id; $option->service_id = $service->id;
$option->fill($data); $option->fill($data);
$option->save(); $option->save();
@ -72,7 +72,7 @@ class Option
public function delete($id) public function delete($id)
{ {
$option = Models\ServiceOptions::findOrFail($id); $option = Models\ServiceOption::findOrFail($id);
$servers = Models\Server::where('option', $option->id)->get(); $servers = Models\Server::where('option', $option->id)->get();
if (count($servers) !== 0) { if (count($servers) !== 0) {
@ -82,7 +82,7 @@ class Option
DB::beginTransaction(); DB::beginTransaction();
try { try {
Models\ServiceVariables::where('option_id', $option->id)->delete(); Models\ServiceVariable::where('option_id', $option->id)->delete();
$option->delete(); $option->delete();
DB::commit(); DB::commit();
@ -94,7 +94,7 @@ class Option
public function update($id, array $data) public function update($id, array $data)
{ {
$option = Models\ServiceOptions::findOrFail($id); $option = Models\ServiceOption::findOrFail($id);
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'name' => 'sometimes|required|string|max:255', 'name' => 'sometimes|required|string|max:255',

View file

@ -49,12 +49,6 @@ class Pack
'option' => 'required|exists:service_options,id', 'option' => 'required|exists:service_options,id',
'selectable' => 'sometimes|boolean', 'selectable' => 'sometimes|boolean',
'visible' => 'sometimes|boolean', 'visible' => 'sometimes|boolean',
'build_memory' => 'required|integer|min:0',
'build_swap' => 'required|integer|min:0',
'build_cpu' => 'required|integer|min:0',
'build_io' => 'required|integer|min:10|max:1000',
'build_container' => 'required|string',
'build_script' => 'sometimes|nullable|string',
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
@ -66,11 +60,8 @@ class Pack
throw new DisplayException('The file provided does not appear to be valid.'); throw new DisplayException('The file provided does not appear to be valid.');
} }
if (! in_array($data['file_upload']->getMimeType(), [ if (! in_array($data['file_upload']->getMimeType(), ['application/gzip', 'application/x-gzip'])) {
'application/zip', throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetype of application/gzip.');
'application/gzip',
])) {
throw new DisplayException('The file provided does not meet the required filetypes of application/zip or application/gzip.');
} }
} }
@ -78,14 +69,8 @@ class Pack
try { try {
$uuid = new UuidService; $uuid = new UuidService;
$pack = Models\ServicePack::create([ $pack = Models\ServicePack::create([
'option' => $data['option'], 'option_id' => $data['option'],
'uuid' => $uuid->generate('servers', 'uuid'), 'uuid' => $uuid->generate('service_packs', 'uuid'),
'build_memory' => $data['build_memory'],
'build_swap' => $data['build_swap'],
'build_cpu' => $data['build_swap'],
'build_io' => $data['build_io'],
'build_script' => (empty($data['build_script'])) ? null : $data['build_script'],
'build_container' => $data['build_container'],
'name' => $data['name'], 'name' => $data['name'],
'version' => $data['version'], 'version' => $data['version'],
'description' => (empty($data['description'])) ? null : $data['description'], 'description' => (empty($data['description'])) ? null : $data['description'],
@ -95,8 +80,7 @@ class Pack
Storage::makeDirectory('packs/' . $pack->uuid); Storage::makeDirectory('packs/' . $pack->uuid);
if (isset($data['file_upload'])) { if (isset($data['file_upload'])) {
$filename = ($data['file_upload']->getMimeType() === 'application/zip') ? 'archive.zip' : 'archive.tar.gz'; $data['file_upload']->storeAs('packs/' . $pack->uuid, 'archive.tar.gz');
$data['file_upload']->storeAs('packs/' . $pack->uuid, $filename);
} }
DB::commit(); DB::commit();
@ -105,7 +89,7 @@ class Pack
throw $ex; throw $ex;
} }
return $pack->id; return $pack;
} }
public function createWithTemplate(array $data) public function createWithTemplate(array $data)
@ -132,38 +116,30 @@ class Pack
throw new DisplayException('The uploaded archive was unable to be opened.'); throw new DisplayException('The uploaded archive was unable to be opened.');
} }
$isZip = $zip->locateName('archive.zip');
$isTar = $zip->locateName('archive.tar.gz'); $isTar = $zip->locateName('archive.tar.gz');
if ($zip->locateName('import.json') === false || ($isZip === false && $isTar === false)) { if (! $zip->locateName('import.json') || ! $isTar) {
throw new DisplayException('This contents of the provided archive were in an invalid format.'); throw new DisplayException('This contents of the provided archive were in an invalid format.');
} }
$json = json_decode($zip->getFromName('import.json')); $json = json_decode($zip->getFromName('import.json'));
$id = $this->create([ $pack = $this->create([
'name' => $json->name, 'name' => $json->name,
'version' => $json->version, 'version' => $json->version,
'description' => $json->description, 'description' => $json->description,
'option' => $data['option'], 'option' => $data['option'],
'selectable' => $json->selectable, 'selectable' => $json->selectable,
'visible' => $json->visible, 'visible' => $json->visible,
'build_memory' => $json->build->memory,
'build_swap' => $json->build->swap,
'build_cpu' => $json->build->cpu,
'build_io' => $json->build->io,
'build_container' => $json->build->container,
'build_script' => $json->build->script,
]); ]);
$pack = Models\ServicePack::findOrFail($id); if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) {
if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), ($isZip === false) ? 'archive.tar.gz' : 'archive.zip')) {
$pack->delete(); $pack->delete();
throw new DisplayException('Unable to extract the archive file to the correct location.'); throw new DisplayException('Unable to extract the archive file to the correct location.');
} }
$zip->close(); $zip->close();
return $pack->id; return $pack;
} else { } else {
$json = json_decode(file_get_contents($data['file_upload']->path())); $json = json_decode(file_get_contents($data['file_upload']->path()));
@ -174,12 +150,6 @@ class Pack
'option' => $data['option'], 'option' => $data['option'],
'selectable' => $json->selectable, 'selectable' => $json->selectable,
'visible' => $json->visible, 'visible' => $json->visible,
'build_memory' => $json->build->memory,
'build_swap' => $json->build->swap,
'build_cpu' => $json->build->cpu,
'build_io' => $json->build->io,
'build_container' => $json->build->container,
'build_script' => $json->build->script,
]); ]);
} }
} }
@ -193,36 +163,20 @@ class Pack
'option' => 'required|exists:service_options,id', 'option' => 'required|exists:service_options,id',
'selectable' => 'sometimes|boolean', 'selectable' => 'sometimes|boolean',
'visible' => 'sometimes|boolean', 'visible' => 'sometimes|boolean',
'build_memory' => 'required|integer|min:0',
'build_swap' => 'required|integer|min:0',
'build_cpu' => 'required|integer|min:0',
'build_io' => 'required|integer|min:10|max:1000',
'build_container' => 'required|string',
'build_script' => 'sometimes|string',
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
throw new DisplayValidationException($validator->errors()); throw new DisplayValidationException($validator->errors());
} }
DB::transaction(function () use ($id, $data) {
Models\ServicePack::findOrFail($id)->update([ Models\ServicePack::findOrFail($id)->update([
'option' => $data['option'], 'option_id' => $data['option'],
'build_memory' => $data['build_memory'],
'build_swap' => $data['build_swap'],
'build_cpu' => $data['build_swap'],
'build_io' => $data['build_io'],
'build_script' => (empty($data['build_script'])) ? null : $data['build_script'],
'build_container' => $data['build_container'],
'name' => $data['name'], 'name' => $data['name'],
'version' => $data['version'], 'version' => $data['version'],
'description' => (empty($data['description'])) ? null : $data['description'], 'description' => (empty($data['description'])) ? null : $data['description'],
'selectable' => isset($data['selectable']), 'selectable' => isset($data['selectable']),
'visible' => isset($data['visible']), 'visible' => isset($data['visible']),
]); ]);
return true;
});
} }
public function delete($id) public function delete($id)

View file

@ -53,25 +53,22 @@ class Service
throw new DisplayValidationException($validator->errors()); throw new DisplayValidationException($validator->errors());
} }
$data['author'] = env('SERVICE_AUTHOR', (string) Uuid::generate(4));
$service = new Models\Service;
DB::beginTransaction(); DB::beginTransaction();
try { try {
$service = new Models\Service;
$service->author = env('SERVICE_AUTHOR', (string) Uuid::generate(4));
$service->fill($data); $service->fill($data);
$service->save(); $service->save();
Storage::put('services/' . $data['file'] . '/main.json', '{}'); Storage::put('services/' . $service->file . '/main.json', '{}');
Storage::copy('services/.templates/index.js', 'services/' . $data['file'] . '/index.js'); Storage::copy('services/.templates/index.js', 'services/' . $service->file . '/index.js');
DB::commit(); DB::commit();
} catch (\Exception $ex) { } catch (\Exception $ex) {
DB::rollBack(); DB::rollBack();
throw $ex; throw $ex;
} }
return $service->id; return $service;
} }
public function update($id, array $data) public function update($id, array $data)
@ -99,7 +96,7 @@ class Service
{ {
$service = Models\Service::findOrFail($id); $service = Models\Service::findOrFail($id);
$servers = Models\Server::where('service', $service->id)->get(); $servers = Models\Server::where('service', $service->id)->get();
$options = Models\ServiceOptions::select('id')->where('parent_service', $service->id); $options = Models\ServiceOption::select('id')->where('service_id', $service->id);
if (count($servers) !== 0) { if (count($servers) !== 0) {
throw new DisplayException('You cannot delete a service that has servers associated with it.'); throw new DisplayException('You cannot delete a service that has servers associated with it.');
@ -107,7 +104,7 @@ class Service
DB::beginTransaction(); DB::beginTransaction();
try { try {
Models\ServiceVariables::whereIn('option_id', $options->get()->toArray())->delete(); Models\ServiceVariable::whereIn('option_id', $options->get()->toArray())->delete();
$options->delete(); $options->delete();
$service->delete(); $service->delete();

View file

@ -39,16 +39,16 @@ class Variable
public function create($id, array $data) public function create($id, array $data)
{ {
$option = Models\ServiceOptions::findOrFail($id); $option = Models\ServiceOption::select('id')->findOrFail($id);
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'name' => 'required|string|min:1|max:255', 'name' => 'required|string|min:1|max:255',
'description' => 'required|string', 'description' => 'required|string',
'env_variable' => 'required|regex:/^[\w]{1,255}$/', 'env_variable' => 'required|regex:/^[\w]{1,255}$/',
'default_value' => 'string|max:255', 'default_value' => 'string|max:255',
'user_viewable' => 'sometimes|required|numeric|size:1', 'user_viewable' => 'sometimes|required|nullable|boolean',
'user_editable' => 'sometimes|required|numeric|size:1', 'user_editable' => 'sometimes|required|nullable|boolean',
'required' => 'sometimes|required|numeric|size:1', 'required' => 'sometimes|required|nullable|boolean',
'regex' => 'required|string|min:1', 'regex' => 'required|string|min:1',
]); ]);
@ -60,28 +60,29 @@ class Variable
throw new DisplayException('The default value you entered cannot violate the regex requirements.'); throw new DisplayException('The default value you entered cannot violate the regex requirements.');
} }
if (Models\ServiceVariables::where('env_variable', $data['env_variable'])->where('option_id', $option->id)->first()) { if (Models\ServiceVariable::where('env_variable', $data['env_variable'])->where('option_id', $option->id)->first()) {
throw new DisplayException('An environment variable with that name already exists for this option.'); throw new DisplayException('An environment variable with that name already exists for this option.');
} }
$data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : 0; $data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : 0;
$data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : 0; $data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : 0;
$data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : 0; $data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : 0;
$data['option_id'] = $option->id;
$variable = new Models\ServiceVariables; $variable = Models\ServiceVariable::create($data);
$variable->option_id = $option->id;
$variable->fill($data);
return $variable->save(); return $variable;
} }
public function delete($id) public function delete($id)
{ {
$variable = Models\ServiceVariables::findOrFail($id); $variable = Models\ServiceVariable::with('serverVariable')->findOrFail($id);
DB::beginTransaction(); DB::beginTransaction();
try { try {
Models\ServerVariables::where('variable_id', $variable->id)->delete(); foreach ($variable->serverVariable as $svar) {
$svar->delete();
}
$variable->delete(); $variable->delete();
DB::commit(); DB::commit();
@ -93,16 +94,16 @@ class Variable
public function update($id, array $data) public function update($id, array $data)
{ {
$variable = Models\ServiceVariables::findOrFail($id); $variable = Models\ServiceVariable::findOrFail($id);
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'name' => 'sometimes|required|string|min:1|max:255', 'name' => 'sometimes|required|string|min:1|max:255',
'description' => 'sometimes|required|string', 'description' => 'sometimes|required|string',
'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/', 'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/',
'default_value' => 'sometimes|string|max:255', 'default_value' => 'sometimes|string|max:255',
'user_viewable' => 'sometimes|required|numeric|boolean', 'user_viewable' => 'sometimes|required|nullable|boolean',
'user_editable' => 'sometimes|required|numeric|boolean', 'user_editable' => 'sometimes|required|nullable|boolean',
'required' => 'sometimes|required|numeric|boolean', 'required' => 'sometimes|required|nullable|boolean',
'regex' => 'sometimes|required|string|min:1', 'regex' => 'sometimes|required|string|min:1',
]); ]);
@ -117,7 +118,7 @@ class Variable
throw new DisplayException('The default value you entered cannot violate the regex requirements.'); throw new DisplayException('The default value you entered cannot violate the regex requirements.');
} }
if (Models\ServiceVariables::where('id', '!=', $variable->id)->where('env_variable', $data['env_variable'])->where('option_id', $variable->option_id)->first()) { if (Models\ServiceVariable::where('id', '!=', $variable->id)->where('env_variable', $data['env_variable'])->where('option_id', $variable->option_id)->first()) {
throw new DisplayException('An environment variable with that name already exists for this option.'); throw new DisplayException('An environment variable with that name already exists for this option.');
} }
@ -125,7 +126,18 @@ class Variable
$data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : $variable->user_editable; $data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : $variable->user_editable;
$data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : $variable->required; $data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : $variable->required;
$variable->fill($data); // Not using $data because the function that passes into this function
// can't do $requst->only() due to the page setup.
$variable->fill([
'name' => $data['name'],
'description' => $data['description'],
'env_variable' => $data['env_variable'],
'default_value' => $data['default_value'],
'user_viewable' => $data['user_viewable'],
'user_editable' => $data['user_editable'],
'required' => $data['required'],
'regex' => $data['regex'],
]);
return $variable->save(); return $variable->save();
} }

View file

@ -25,8 +25,6 @@
namespace Pterodactyl\Repositories; namespace Pterodactyl\Repositories;
use DB; use DB;
use Mail;
use Settings;
use Validator; use Validator;
use Pterodactyl\Models; use Pterodactyl\Models;
use Pterodactyl\Services\UuidService; use Pterodactyl\Services\UuidService;
@ -112,11 +110,11 @@ class SubuserRepository
* @param array $data * @param array $data
* @throws DisplayValidationException * @throws DisplayValidationException
* @throws DisplayException * @throws DisplayException
* @return int Returns the ID of the newly created subuser. * @return \Pterodactyl\Models\Subuser
*/ */
public function create($sid, array $data) public function create($sid, array $data)
{ {
$server = Models\Server::findOrFail($sid); $server = Models\Server::with('node')->findOrFail($sid);
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'permissions' => 'required|array', 'permissions' => 'required|array',
@ -135,32 +133,28 @@ class SubuserRepository
if (! $user) { if (! $user) {
try { try {
$repo = new UserRepository; $repo = new UserRepository;
$uid = $repo->create([ $user = $repo->create([
'email' => $data['email'], 'email' => $data['email'],
'username' => substr(str_replace('@', '', $data['email']), 0, 8), 'username' => str_random(8),
'name_first' => 'John', 'name_first' => 'Unassigned',
'name_last' => 'Doe', 'name_last' => 'Name',
'root_admin' => false, 'root_admin' => false,
]); ]);
$user = Models\User::findOrFail($uid);
} catch (\Exception $ex) { } catch (\Exception $ex) {
throw $ex; throw $ex;
} }
} elseif ($server->owner === $user->id) { } elseif ($server->owner_id === $user->id) {
throw new DisplayException('You cannot add the owner of a server as a subuser.'); throw new DisplayException('You cannot add the owner of a server as a subuser.');
} elseif (Models\Subuser::select('id')->where('user_id', $user->id)->where('server_id', $server->id)->first()) { } elseif (Models\Subuser::select('id')->where('user_id', $user->id)->where('server_id', $server->id)->first()) {
throw new DisplayException('A subuser with that email already exists for this server.'); throw new DisplayException('A subuser with that email already exists for this server.');
} }
$uuid = new UuidService; $uuid = new UuidService;
$subuser = Models\Subuser::create([
$subuser = new Models\Subuser;
$subuser->fill([
'user_id' => $user->id, 'user_id' => $user->id,
'server_id' => $server->id, 'server_id' => $server->id,
'daemonSecret' => (string) $uuid->generate('servers', 'uuid'), 'daemonSecret' => (string) $uuid->generate('servers', 'uuid'),
]); ]);
$subuser->save();
$daemonPermissions = $this->coreDaemonPermissions; $daemonPermissions = $this->coreDaemonPermissions;
foreach ($data['permissions'] as $permission) { foreach ($data['permissions'] as $permission) {
@ -170,13 +164,10 @@ class SubuserRepository
array_push($daemonPermissions, $this->permissions[$permission]); array_push($daemonPermissions, $this->permissions[$permission]);
} }
$model = new Models\Permission; Models\Permission::create([
$model->fill([ 'subuser_id' => $subuser->id,
'user_id' => $user->id,
'server_id' => $server->id,
'permission' => $permission, 'permission' => $permission,
]); ]);
$model->save();
} }
} }
@ -184,14 +175,10 @@ class SubuserRepository
// We contact even if they don't have any daemon permissions to overwrite // We contact even if they don't have any daemon permissions to overwrite
// if they did have them previously. // if they did have them previously.
$node = Models\Node::getByID($server->node); $server->node->guzzleClient([
$client = Models\Node::guzzleRequest($server->node);
$res = $client->request('PATCH', '/server', [
'headers' => [
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
'X-Access-Token' => $node->daemonSecret, 'X-Access-Token' => $server->node->daemonSecret,
], ])->request('PATCH', '/server', [
'json' => [ 'json' => [
'keys' => [ 'keys' => [
$subuser->daemonSecret => $daemonPermissions, $subuser->daemonSecret => $daemonPermissions,
@ -199,18 +186,9 @@ class SubuserRepository
], ],
]); ]);
$email = $data['email'];
Mail::queue('emails.added-subuser', [
'serverName' => $server->name,
'url' => route('server.index', $server->uuidShort),
], function ($message) use ($email) {
$message->to($email);
$message->from(Settings::get('email_from', env('MAIL_FROM')), Settings::get('email_sender_name', env('MAIL_FROM_NAME', 'Pterodactyl Panel')));
$message->subject(Settings::get('company') . ' - Added to Server');
});
DB::commit(); DB::commit();
return $subuser->id; return $subuser;
} catch (\GuzzleHttp\Exception\TransferException $ex) { } catch (\GuzzleHttp\Exception\TransferException $ex) {
DB::rollBack(); DB::rollBack();
throw new DisplayException('There was an error attempting to connect to the daemon to add this user.', $ex); throw new DisplayException('There was an error attempting to connect to the daemon to add this user.', $ex);
@ -232,22 +210,16 @@ class SubuserRepository
*/ */
public function delete($id) public function delete($id)
{ {
$subuser = Models\Subuser::findOrFail($id); $subuser = Models\Subuser::with('server.node')->findOrFail($id);
$server = Models\Server::findOrFail($subuser->server_id); $server = $subuser->server;
DB::beginTransaction(); DB::beginTransaction();
try { try {
Models\Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); $server->node->guzzleClient([
$node = Models\Node::getByID($server->node);
$client = Models\Node::guzzleRequest($server->node);
$res = $client->request('PATCH', '/server', [
'headers' => [
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
'X-Access-Token' => $node->daemonSecret, 'X-Access-Token' => $server->node->daemonSecret,
], ])->request('PATCH', '/server', [
'json' => [ 'json' => [
'keys' => [ 'keys' => [
$subuser->daemonSecret => [], $subuser->daemonSecret => [],
@ -255,6 +227,9 @@ class SubuserRepository
], ],
]); ]);
foreach ($subuser->permissions as &$permission) {
$permission->delete();
}
$subuser->delete(); $subuser->delete();
DB::commit(); DB::commit();
@ -290,13 +265,15 @@ class SubuserRepository
throw new DisplayValidationException(json_encode($validator->all())); throw new DisplayValidationException(json_encode($validator->all()));
} }
$subuser = Models\Subuser::findOrFail($id); $subuser = Models\Subuser::with('server.node')->findOrFail($id);
$server = Models\Server::findOrFail($data['server']); $server = $subuser->server;
DB::beginTransaction(); DB::beginTransaction();
try { try {
Models\Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); foreach ($subuser->permissions as &$permission) {
$permission->delete();
}
$daemonPermissions = $this->coreDaemonPermissions; $daemonPermissions = $this->coreDaemonPermissions;
foreach ($data['permissions'] as $permission) { foreach ($data['permissions'] as $permission) {
@ -305,27 +282,20 @@ class SubuserRepository
if (! is_null($this->permissions[$permission])) { if (! is_null($this->permissions[$permission])) {
array_push($daemonPermissions, $this->permissions[$permission]); array_push($daemonPermissions, $this->permissions[$permission]);
} }
$model = new Models\Permission; Models\Permission::create([
$model->fill([ 'subuser_id' => $subuser->id,
'user_id' => $data['user'],
'server_id' => $data['server'],
'permission' => $permission, 'permission' => $permission,
]); ]);
$model->save();
} }
} }
// Contact Daemon // Contact Daemon
// We contact even if they don't have any daemon permissions to overwrite // We contact even if they don't have any daemon permissions to overwrite
// if they did have them previously. // if they did have them previously.
$node = Models\Node::getByID($server->node); $server->node->guzzleClient([
$client = Models\Node::guzzleRequest($server->node);
$res = $client->request('PATCH', '/server', [
'headers' => [
'X-Access-Server' => $server->uuid, 'X-Access-Server' => $server->uuid,
'X-Access-Token' => $node->daemonSecret, 'X-Access-Token' => $server->node->daemonSecret,
], ])->request('PATCH', '/server', [
'json' => [ 'json' => [
'keys' => [ 'keys' => [
$subuser->daemonSecret => $daemonPermissions, $subuser->daemonSecret => $daemonPermissions,

View file

@ -34,7 +34,6 @@ use Validator;
use Pterodactyl\Models; use Pterodactyl\Models;
use Pterodactyl\Services\UuidService; use Pterodactyl\Services\UuidService;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Notifications\AccountCreated;
use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Exceptions\DisplayValidationException;
class UserRepository class UserRepository
@ -105,13 +104,11 @@ class UserRepository
'token' => $token, 'token' => $token,
'created_at' => Carbon::now()->toDateTimeString(), 'created_at' => Carbon::now()->toDateTimeString(),
]); ]);
$user->notify((new AccountCreated($token)));
} }
DB::commit(); DB::commit();
return $user->id; return $user;
} catch (\Exception $ex) { } catch (\Exception $ex) {
DB::rollBack(); DB::rollBack();
throw $ex; throw $ex;
@ -167,7 +164,7 @@ class UserRepository
*/ */
public function delete($id) public function delete($id)
{ {
if (Models\Server::where('owner', $id)->count() > 0) { if (Models\Server::where('owner_id', $id)->count() > 0) {
throw new DisplayException('Cannot delete a user with active servers attached to thier account.'); throw new DisplayException('Cannot delete a user with active servers attached to thier account.');
} }
@ -179,10 +176,15 @@ class UserRepository
DB::beginTransaction(); DB::beginTransaction();
try { try {
Models\Permission::where('user_id', $id)->delete(); foreach (Models\Subuser::with('permissions')->where('user_id', $id)->get() as &$subuser) {
Models\Subuser::where('user_id', $id)->delete(); foreach ($subuser->permissions as &$permission) {
Models\User::destroy($id); $permission->delete();
}
$subuser->delete();
}
Models\User::destroy($id);
DB::commit(); DB::commit();
return true; return true;

View file

@ -65,7 +65,7 @@ class DeploymentService
throw new DisplayException('The location passed was not valid and could not be found.'); throw new DisplayException('The location passed was not valid and could not be found.');
} }
$node = Models\Node::where('location', $useLocation->id)->where('public', 1)->whereNotIn('id', $not)->inRandomOrder()->first(); $node = Models\Node::where('location_id', $useLocation->id)->where('public', 1)->whereNotIn('id', $not)->inRandomOrder()->first();
if (! $node) { if (! $node) {
throw new DisplayException("Unable to find a node in location {$useLocation->short} (id: {$useLocation->id}) that is available and has space."); throw new DisplayException("Unable to find a node in location {$useLocation->short} (id: {$useLocation->id}) that is available and has space.");
} }
@ -107,7 +107,7 @@ class DeploymentService
*/ */
public static function randomAllocation($node) public static function randomAllocation($node)
{ {
$allocation = Models\Allocation::where('node', $node)->whereNull('assigned_to')->inRandomOrder()->first(); $allocation = Models\Allocation::where('node_id', $node)->whereNull('server_id')->inRandomOrder()->first();
if (! $allocation) { if (! $allocation) {
throw new DisplayException('No available allocation could be found for the assigned node.'); throw new DisplayException('No available allocation could be found for the assigned node.');
} }
@ -125,7 +125,7 @@ class DeploymentService
protected static function checkNodeAllocation(Models\Node $node, $memory, $disk) protected static function checkNodeAllocation(Models\Node $node, $memory, $disk)
{ {
if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) { if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) {
$totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(); $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first();
// Check memory limits // Check memory limits
if (is_numeric($node->memory_overallocate)) { if (is_numeric($node->memory_overallocate)) {

Some files were not shown because too many files have changed in this diff Show more