From 7529e961de72a5326104a9b29f1d4c5c0c7e4d03 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 5 Sep 2016 16:21:36 -0400 Subject: [PATCH] Add back API (#80) Re-implements the API after it was removed in the Laravel 5.3 upgrade. --- CHANGELOG.md | 3 +- app/Http/Controllers/API/BaseController.php | 32 ++ .../Controllers/API/LocationController.php | 64 ++++ app/Http/Controllers/API/NodeController.php | 203 +++++++++++++ app/Http/Controllers/API/ServerController.php | 282 ++++++++++++++++++ .../Controllers/API/ServiceController.php | 68 +++++ app/Http/Controllers/API/UserController.php | 207 +++++++++++++ app/Http/Controllers/Admin/APIController.php | 8 +- app/Http/Middleware/APISecretToken.php | 122 ++++++++ app/Http/Routes/APIRoutes.php | 160 ++++++++++ app/Models/Server.php | 5 +- app/Providers/RouteServiceProvider.php | 36 +-- app/Repositories/APIRepository.php | 28 +- app/Repositories/ServerRepository.php | 35 ++- app/Repositories/UserRepository.php | 4 +- app/Transformers/AllocationTransformer.php | 42 +++ app/Transformers/NodeTransformer.php | 42 +++ app/Transformers/ServerTransformer.php | 42 +++ app/Transformers/UserTransformer.php | 42 +++ config/api.php | 220 ++++++++++++++ config/app.php | 2 + resources/views/admin/api/index.blade.php | 2 +- resources/views/admin/api/new.blade.php | 52 ++-- 23 files changed, 1620 insertions(+), 81 deletions(-) create mode 100755 app/Http/Controllers/API/BaseController.php create mode 100755 app/Http/Controllers/API/LocationController.php create mode 100755 app/Http/Controllers/API/NodeController.php create mode 100755 app/Http/Controllers/API/ServerController.php create mode 100755 app/Http/Controllers/API/ServiceController.php create mode 100755 app/Http/Controllers/API/UserController.php create mode 100755 app/Http/Middleware/APISecretToken.php create mode 100755 app/Http/Routes/APIRoutes.php create mode 100644 app/Transformers/AllocationTransformer.php create mode 100755 app/Transformers/NodeTransformer.php create mode 100755 app/Transformers/ServerTransformer.php create mode 100755 app/Transformers/UserTransformer.php create mode 100644 config/api.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a90220190..6228700ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This file is a running track of new features and fixes to each version of the pa * Adds support for IP Aliases on display pages for users. This makes it possible to use GRE tunnels and still show the user what IP they should be connecting to. * Adds support for suspending servers * Adds support for viewing SFTP password within the panel (#74, thanks @ET-Bent) +* Improved API with support for server suspension and build modification. ### Bug Fixes * Fixes password auto-generation on 'Manage Server' page. (#67, thanks @ET-Bent) @@ -19,4 +20,4 @@ This file is a running track of new features and fixes to each version of the pa * Fixes a few display issues relating to subusers and database management. ### General -* Update Laravel to version `5.3` and update dependencies. **[BREAKING]** This removes the remote API from the panel due to Dingo API instability. This message will be removed when it is added back. +* Update Laravel to version `5.3` and update dependencies. diff --git a/app/Http/Controllers/API/BaseController.php b/app/Http/Controllers/API/BaseController.php new file mode 100755 index 000000000..7d897fe55 --- /dev/null +++ b/app/Http/Controllers/API/BaseController.php @@ -0,0 +1,32 @@ + + * + * 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\API; + +use Dingo\Api\Routing\Helpers; +use Illuminate\Routing\Controller; + +class BaseController extends Controller +{ + use Helpers; +} diff --git a/app/Http/Controllers/API/LocationController.php b/app/Http/Controllers/API/LocationController.php new file mode 100755 index 000000000..edb6e2919 --- /dev/null +++ b/app/Http/Controllers/API/LocationController.php @@ -0,0 +1,64 @@ + + * + * 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\API; + +use DB; +use Illuminate\Http\Request; +use Pterodactyl\Models\Location; + +/** + * @Resource("Servers") + */ +class LocationController extends BaseController +{ + + public function __construct() + { + // + } + + /** + * List All Locations + * + * Lists all locations currently on the system. + * + * @Get("/locations") + * @Versions({"v1"}) + * @Response(200) + */ + public function list(Request $request) + { + $locations = Location::select('locations.*', DB::raw('GROUP_CONCAT(nodes.id) as nodes')) + ->join('nodes', 'locations.id', '=', 'nodes.location') + ->groupBy('locations.id') + ->get(); + + foreach($locations as &$location) { + $location->nodes = explode(',', $location->nodes); + } + + return $locations; + } + +} diff --git a/app/Http/Controllers/API/NodeController.php b/app/Http/Controllers/API/NodeController.php new file mode 100755 index 000000000..0351735d4 --- /dev/null +++ b/app/Http/Controllers/API/NodeController.php @@ -0,0 +1,203 @@ + + * + * 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\API; + +use Illuminate\Http\Request; + +use Pterodactyl\Models; +use Pterodactyl\Transformers\NodeTransformer; +use Pterodactyl\Transformers\AllocationTransformer; +use Pterodactyl\Repositories\NodeRepository; + +use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Exceptions\DisplayException; +use Dingo\Api\Exception\ResourceException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; + +/** + * @Resource("Servers") + */ +class NodeController extends BaseController +{ + + public function __construct() + { + // + } + + /** + * List All Nodes + * + * Lists all nodes currently on the system. + * + * @Get("/nodes/{?page}") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("page", type="integer", description="The page of results to view.", default=1) + * }) + * @Response(200) + */ + public function list(Request $request) + { + $nodes = Models\Node::paginate(50); + return $this->response->paginator($nodes, new NodeTransformer); + } + + /** + * Create a New Node + * + * @Post("/nodes") + * @Versions({"v1"}) + * @Transaction({ + * @Request({ + * 'name' => 'My API Node', + * 'location' => 1, + * 'public' => 1, + * 'fqdn' => 'daemon.wuzzle.woo', + * 'scheme' => 'https', + * 'memory' => 10240, + * 'memory_overallocate' => 100, + * 'disk' => 204800, + * 'disk_overallocate' => -1, + * 'daemonBase' => '/srv/daemon-data', + * 'daemonSFTP' => 2022, + * 'daemonListen' => 8080 + * }, headers={"Authorization": "Bearer "}), + * @Response(201), + * @Response(422, body={ + * "message": "A validation error occured.", + * "errors": {}, + * "status_code": 422 + * }), + * @Response(503, body={ + * "message": "There was an error while attempting to add this node to the system.", + * "status_code": 503 + * }) + * }) + */ + public function create(Request $request) + { + try { + $node = new NodeRepository; + $new = $node->create($request->all()); + return $this->response->created(route('api.nodes.view', [ + 'id' => $new + ])); + } catch (DisplayValidationException $ex) { + throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $e) { + throw new BadRequestHttpException('There was an error while attempting to add this node to the system.'); + } + } + + /** + * List Specific Node + * + * Lists specific fields about a server or all fields pertaining to that node. + * + * @Get("/nodes/{id}/{?fields}") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the node to get information on."), + * @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.") + * }) + * @Response(200) + */ + public function view(Request $request, $id, $fields = null) + { + $query = Models\Node::where('id', $id); + + 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 node by that ID was found.'); + } + + return [ + 'node' => $query->first(), + 'allocations' => [ + 'assigned' => Models\Allocation::where('node', $id)->whereNotNull('assigned_to')->get(), + 'unassigned' => Models\Allocation::where('node', $id)->whereNull('assigned_to')->get() + ] + ]; + } catch (NotFoundHttpException $ex) { + throw $ex; + } catch (\Exception $ex) { + throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); + } + } + + /** + * List all Node Allocations + * + * Returns a listing of all allocations for every node. + * + * @Get("/nodes/allocations") + * @Versions({"v1"}) + * @Response(200) + */ + public function allocations(Request $request) + { + $allocations = Models\Allocation::paginate(100); + if ($allocations->count() < 1) { + throw new NotFoundHttpException('No allocations have been created.'); + } + return $this->response->paginator($allocations, new AllocationTransformer); + } + + /** + * Delete Node + * + * @Delete("/nodes/{id}") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the node."), + * }) + * @Response(204) + */ + public function delete(Request $request, $id) + { + try { + $node = new NodeRepository; + $node->delete($id); + return $this->response->noContent(); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch(\Exception $e) { + throw new ServiceUnavailableHttpException('An error occured while attempting to delete this node.'); + } + } + +} diff --git a/app/Http/Controllers/API/ServerController.php b/app/Http/Controllers/API/ServerController.php new file mode 100755 index 000000000..a6d86cac4 --- /dev/null +++ b/app/Http/Controllers/API/ServerController.php @@ -0,0 +1,282 @@ + + * + * 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\API; + +use Illuminate\Http\Request; + +use Log; +use Pterodactyl\Models; +use Pterodactyl\Transformers\ServerTransformer; +use Pterodactyl\Repositories\ServerRepository; + +use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Exceptions\DisplayException; +use Dingo\Api\Exception\ResourceException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; + +/** + * @Resource("Servers") + */ +class ServerController extends BaseController +{ + + public function __construct() + { + // + } + + /** + * List All Servers + * + * Lists all servers currently on the system. + * + * @Get("/servers/{?page}") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("page", type="integer", description="The page of results to view.", default=1) + * }) + * @Response(200) + */ + public function list(Request $request) + { + $servers = Models\Server::paginate(50); + return $this->response->paginator($servers, new ServerTransformer); + } + + /** + * Create Server + * + * @Post("/servers") + * @Versions({"v1"}) + * @Response(201) + */ + public function create(Request $request) + { + try { + $server = new ServerRepository; + $new = $server->create($request->all()); + return $this->response->created(route('api.servers.view', [ + 'id' => $new + ])); + } catch (DisplayValidationException $ex) { + throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $ex) { + Log::error($ex); + throw new BadRequestHttpException('There was an error while attempting to add this server to the system.'); + } + } + + /** + * List Specific Server + * + * Lists specific fields about a server or all fields pertaining to that server. + * + * @Get("/servers/{id}{?fields}") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the server to get information on."), + * @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.") + * }) + * @Response(200) + */ + public function view(Request $request, $id) + { + $query = Models\Server::where('id', $id); + + 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.'); + } + return $query->first(); + } catch (NotFoundHttpException $ex) { + throw $ex; + } catch (\Exception $ex) { + throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); + } + } + + /** + * Update Server configuration + * + * Updates display information on panel. + * + * @Patch("/servers/{id}/config") + * @Versions({"v1"}) + * @Transaction({ + * @Request({ + * "owner": "new@email.com", + * "name": "New Name", + * "reset_token": true + * }, headers={"Authorization": "Bearer "}), + * @Response(200, body={"name": "New Name"}), + * @Response(422) + * }) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the server to modify.") + * }) + */ + public function config(Request $request, $id) + { + try { + $server = new ServerRepository; + $server->updateDetails($id, $request->all()); + return Models\Server::findOrFail($id); + } catch (DisplayValidationException $ex) { + throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $ex) { + throw new ServiceUnavailableHttpException('Unable to update server on system due to an error.'); + } + } + + /** + * Update Server Build Configuration + * + * Updates server build information on panel and on node. + * + * @Patch("/servers/{id}/build") + * @Versions({"v1"}) + * @Transaction({ + * @Request({ + * "default": "192.168.0.1:25565", + * "add_additional": [ + * "192.168.0.1:25566", + * "192.168.0.1:25567", + * "192.168.0.1:25568" + * ], + * "remove_additional": [], + * "memory": 1024, + * "swap": 0, + * "io": 500, + * "cpu": 0, + * "disk": 1024 + * }, headers={"Authorization": "Bearer "}), + * @Response(200, body={"name": "New Name"}), + * @Response(422) + * }) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the server to modify.") + * }) + */ + public function build(Request $request, $id) + { + try { + throw new BadRequestHttpException('There was an error while attempting to add this node to the system.'); + + $server = new ServerRepository; + $server->changeBuild($id, $request->all()); + return Models\Server::findOrFail($id); + } catch (DisplayValidationException $ex) { + throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $ex) { + throw new ServiceUnavailableHttpException('Unable to update server on system due to an error.'); + } + } + + /** + * Suspend Server + * + * @Post("/servers/{id}/suspend") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the server."), + * }) + * @Response(204) + */ + public function suspend(Request $request, $id) + { + try { + $server = new ServerRepository; + $server->suspend($id); + return $this->response->noContent(); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $ex) { + throw new ServiceUnavailableHttpException('An error occured while attempting to suspend this server instance.'); + } + } + + /** + * Unsuspend Server + * + * @Post("/servers/{id}/unsuspend") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the server."), + * }) + * @Response(204) + */ + public function unsuspend(Request $request, $id) + { + try { + $server = new ServerRepository; + $server->unsuspend($id); + return $this->response->noContent(); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $ex) { + throw new ServiceUnavailableHttpException('An error occured while attempting to unsuspend this server instance.'); + } + } + + /** + * Delete Server + * + * @Delete("/servers/{id}/{force}") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the server."), + * @Parameter("force", type="string", required=false, description="Use 'force' if the server should be removed regardless of daemon response."), + * }) + * @Response(204) + */ + public function delete(Request $request, $id, $force = null) + { + try { + $server = new ServerRepository; + $server->deleteServer($id, $force); + return $this->response->noContent(); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch(\Exception $e) { + throw new ServiceUnavailableHttpException('An error occured while attempting to delete this server.'); + } + } + +} diff --git a/app/Http/Controllers/API/ServiceController.php b/app/Http/Controllers/API/ServiceController.php new file mode 100755 index 000000000..661a461db --- /dev/null +++ b/app/Http/Controllers/API/ServiceController.php @@ -0,0 +1,68 @@ + + * + * 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\API; + +use Illuminate\Http\Request; + +use Pterodactyl\Models; +use Pterodactyl\Transformers\ServiceTransformer; + +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * @Resource("Services") + */ +class ServiceController extends BaseController +{ + + public function __construct() + { + // + } + + public function list(Request $request) + { + return Models\Service::all(); + } + + public function view(Request $request, $id) + { + $service = Models\Service::find($id); + if (!$service) { + throw new NotFoundHttpException('No service by that ID was found.'); + } + + $options = Models\ServiceOptions::select('id', 'name', 'description', 'tag', 'docker_image')->where('parent_service', $service->id)->get(); + foreach($options as &$opt) { + $opt->variables = Models\ServiceVariables::where('option_id', $opt->id)->get(); + } + + return [ + 'service' => $service, + 'options' => $options + ]; + + } + +} diff --git a/app/Http/Controllers/API/UserController.php b/app/Http/Controllers/API/UserController.php new file mode 100755 index 000000000..e32696333 --- /dev/null +++ b/app/Http/Controllers/API/UserController.php @@ -0,0 +1,207 @@ + + * + * 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\API; + +use Illuminate\Http\Request; + +use Dingo\Api\Exception\ResourceException; + +use Pterodactyl\Models; +use Pterodactyl\Transformers\UserTransformer; +use Pterodactyl\Repositories\UserRepository; + +use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Exceptions\DisplayException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; + +/** + * @Resource("Users") + */ +class UserController extends BaseController +{ + + public function __construct() + { + + } + + /** + * List All Users + * + * Lists all users currently on the system. + * + * @Get("/users/{?page}") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("page", type="integer", description="The page of results to view.", default=1) + * }) + * @Response(200) + */ + public function list(Request $request) + { + $users = Models\User::paginate(50); + return $this->response->paginator($users, new UserTransformer); + } + + /** + * List Specific User + * + * Lists specific fields about a user or all fields pertaining to that user. + * + * @Get("/users/{id}/{fields}") + * @Versions({"v1"}) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the user to get information on."), + * @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.") + * }) + * @Response(200) + */ + public function view(Request $request, $id) + { + $query = Models\User::where('id', $id); + + 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.'); + } + return $query->first(); + } catch (NotFoundHttpException $ex) { + throw $ex; + } catch (\Exception $ex) { + throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); + } + + } + + /** + * Create a New User + * + * @Post("/users") + * @Versions({"v1"}) + * @Transaction({ + * @Request({ + * "email": "foo@example.com", + * "password": "foopassword", + * "admin": false + * }, headers={"Authorization": "Bearer "}), + * @Response(201), + * @Response(422, body={ + * "message": "A validation error occured.", + * "errors": { + * "email": {"The email field is required."}, + * "password": {"The password field is required."}, + * "admin": {"The admin field is required."} + * }, + * "status_code": 422 + * }) + * }) + */ + public function create(Request $request) + { + try { + $user = new UserRepository; + $create = $user->create($request->input('email'), $request->input('password'), $request->input('admin')); + return $this->response->created(route('api.users.view', [ + 'id' => $create + ])); + } catch (DisplayValidationException $ex) { + throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $ex) { + throw new ServiceUnavailableHttpException('Unable to create a user on the system due to an error.'); + } + } + + /** + * Update an Existing User + * + * The data sent in the request will be used to update the existing user on the system. + * + * @Patch("/users/{id}") + * @Versions({"v1"}) + * @Transaction({ + * @Request({ + * "email": "new@email.com" + * }, headers={"Authorization": "Bearer "}), + * @Response(200, body={"email": "new@email.com"}), + * @Response(422) + * }) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the user to modify.") + * }) + */ + public function update(Request $request, $id) + { + try { + $user = new UserRepository; + $user->update($id, $request->all()); + return Models\User::findOrFail($id); + } catch (DisplayValidationException $ex) { + throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $ex) { + throw new ServiceUnavailableHttpException('Unable to update a user on the system due to an error.'); + } + } + + /** + * Delete a User + * + * @Delete("/users/{id}") + * @Versions({"v1"}) + * @Transaction({ + * @Request(headers={"Authorization": "Bearer "}), + * @Response(204), + * @Response(422) + * }) + * @Parameters({ + * @Parameter("id", type="integer", required=true, description="The ID of the user to delete.") + * }) + */ + public function delete(Request $request, $id) + { + try { + $user = new UserRepository; + $user->delete($id); + return $this->response->noContent(); + } catch (DisplayException $ex) { + throw new ResourceException($ex->getMessage()); + } catch (\Exception $ex) { + throw new ServiceUnavailableHttpException('Unable to delete this user due to an error.'); + } + } + +} diff --git a/app/Http/Controllers/Admin/APIController.php b/app/Http/Controllers/Admin/APIController.php index 2910c8ede..0d1684484 100644 --- a/app/Http/Controllers/Admin/APIController.php +++ b/app/Http/Controllers/Admin/APIController.php @@ -65,7 +65,13 @@ class APIController extends Controller try { $api = new APIRepository; $secret = $api->new($request->except(['_token'])); - Alert::info('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

Secret: ' . $secret . '')->flash(); + // Alert::info('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

Secret: ' . $secret . '')->flash(); + Alert::info("")->flash(); return redirect()->route('admin.api'); } catch (DisplayValidationException $ex) { return redirect()->route('admin.api.new')->withErrors(json_decode($ex->getMessage()))->withInput(); diff --git a/app/Http/Middleware/APISecretToken.php b/app/Http/Middleware/APISecretToken.php new file mode 100755 index 000000000..71c148986 --- /dev/null +++ b/app/Http/Middleware/APISecretToken.php @@ -0,0 +1,122 @@ + + * + * 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\Middleware; + +use Crypt; +use IPTools\IP; +use IPTools\Range; + +use Pterodactyl\Models\APIKey; +use Pterodactyl\Models\APIPermission; + +use Illuminate\Http\Request; +use Dingo\Api\Routing\Route; +use Dingo\Api\Auth\Provider\Authorization; + +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; // 400 +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; // 401 +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; // 403 +use Symfony\Component\HttpKernel\Exception\HttpException; //500 + +class APISecretToken extends Authorization +{ + + protected $algo = 'sha256'; + + protected $permissionAllowed = false; + + protected $url = ''; + + public function __construct() + { + // + } + + public function getAuthorizationMethod() + { + return 'Authorization'; + } + + public function authenticate(Request $request, Route $route) + { + if (!$request->bearerToken() || empty($request->bearerToken())) { + throw new UnauthorizedHttpException('The authentication header was missing or malformed'); + } + + list($public, $hashed) = explode('.', $request->bearerToken()); + + $key = APIKey::where('public', $public)->first(); + if (!$key) { + throw new AccessDeniedHttpException('Invalid API Key.'); + } + + // Check for Resource Permissions + if (!empty($request->route()->getName())) { + if(!is_null($key->allowed_ips)) { + $inRange = false; + foreach(json_decode($key->allowed_ips) as $ip) { + if (Range::parse($ip)->contains(new IP($request->ip()))) { + $inRange = true; + break; + } + } + if (!$inRange) { + throw new AccessDeniedHttpException('This IP address <' . $request->ip() . '> does not have permission to use this API key.'); + } + } + + foreach(APIPermission::where('key_id', $key->id)->get() as &$row) { + if ($row->permission === '*' || $row->permission === $request->route()->getName()) { + $this->permissionAllowed = true; + continue; + } + } + + if (!$this->permissionAllowed) { + throw new AccessDeniedHttpException('You do not have permission to access this resource.'); + } + } + + try { + $decrypted = Crypt::decrypt($key->secret); + } catch (\Illuminate\Contracts\Encryption\DecryptException $ex) { + throw new HttpException('There was an error while attempting to check your secret key.'); + } + + $this->url = urldecode($request->fullUrl()); + if($this->_generateHMAC($request->getContent(), $decrypted) !== base64_decode($hashed)) { + throw new BadRequestHttpException('The hashed body was not valid. Potential modification of contents in route.'); + } + + return true; + + } + + protected function _generateHMAC($body, $key) + { + $data = $this->url . $body; + return hash_hmac($this->algo, $data, $key, true); + } + +} diff --git a/app/Http/Routes/APIRoutes.php b/app/Http/Routes/APIRoutes.php new file mode 100755 index 000000000..f50a8e072 --- /dev/null +++ b/app/Http/Routes/APIRoutes.php @@ -0,0 +1,160 @@ + + * + * 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\Routes; + +use Pterodactyl\Models; +use Illuminate\Routing\Router; + +class APIRoutes +{ + + public function map(Router $router) { + + $api = app('Dingo\Api\Routing\Router'); + $api->version('v1', ['middleware' => 'api.auth'], function ($api) { + + /** + * User Routes + */ + $api->get('users', [ + 'as' => 'api.users.list', + 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@list' + ]); + + $api->post('users', [ + 'as' => 'api.users.create', + 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@create' + ]); + + $api->get('users/{id}', [ + 'as' => 'api.users.view', + 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@view' + ]); + + $api->patch('users/{id}', [ + 'as' => 'api.users.update', + 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@update' + ]); + + $api->delete('users/{id}', [ + 'as' => 'api.users.delete', + 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@delete' + ]); + + /** + * Server Routes + */ + $api->get('servers', [ + 'as' => 'api.servers.list', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@list' + ]); + + $api->post('servers', [ + 'as' => 'api.servers.create', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@create' + ]); + + $api->get('servers/{id}', [ + 'as' => 'api.servers.view', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@view' + ]); + + $api->patch('servers/{id}/config', [ + 'as' => 'api.servers.config', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@config' + ]); + + $api->patch('servers/{id}/build', [ + 'as' => 'api.servers.build', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@build' + ]); + + $api->post('servers/{id}/suspend', [ + 'as' => 'api.servers.suspend', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@suspend' + ]); + + $api->post('servers/{id}/unsuspend', [ + 'as' => 'api.servers.unsuspend', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@unsuspend' + ]); + + $api->delete('servers/{id}/{force?}', [ + 'as' => 'api.servers.delete', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@delete' + ]); + + /** + * Node Routes + */ + $api->get('nodes', [ + 'as' => 'api.nodes.list', + 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@list' + ]); + + $api->post('nodes', [ + 'as' => 'api.nodes.create', + 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@create' + ]); + + $api->get('nodes/allocations', [ + 'as' => 'api.nodes.allocations', + 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@allocations' + ]); + + $api->get('nodes/{id}', [ + 'as' => 'api.nodes.view', + 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@view' + ]); + + $api->delete('nodes/{id}', [ + 'as' => 'api.nodes.delete', + 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@delete' + ]); + + /** + * Location Routes + */ + $api->get('locations', [ + 'as' => 'api.locations.list', + 'uses' => 'Pterodactyl\Http\Controllers\API\LocationController@list' + ]); + + /** + * Service Routes + */ + $api->get('services', [ + 'as' => 'api.services.list', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@list' + ]); + + $api->get('services/{id}', [ + 'as' => 'api.services.view', + 'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@view' + ]); + + }); + } + +} diff --git a/app/Models/Server.php b/app/Models/Server.php index 171e62194..655323e0c 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -43,7 +43,10 @@ class Server extends Model * * @var array */ - protected $hidden = ['daemonSecret']; + protected $hidden = [ + 'daemonSecret', + 'sftp_password' + ]; /** * Fields that are not mass assignable. diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 9a3305c2b..f25c59471 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -36,44 +36,10 @@ class RouteServiceProvider extends ServiceProvider */ public function map() { - $this->mapWebRoutes(); - $this->mapApiRoutes(); - } - /** - * Define the "web" routes for the application. - * - * These routes all receive session state, CSRF protection, etc. - * - * @return void - */ - protected function mapWebRoutes() - { - Route::group([ - // 'middleware' => 'web', - 'namespace' => $this->namespace, - ], function ($router) { + Route::group(['namespace' => $this->namespace], function ($router) { foreach (glob(app_path('Http//Routes') . '/*.php') as $file) { $this->app->make('Pterodactyl\\Http\\Routes\\' . basename($file, '.php'))->map($router); } }); } - /** - * Define the "api" routes for the application. - * - * These routes are typically stateless. - * - * @return void - */ - protected function mapApiRoutes() - { - Route::group([ - 'middleware' => 'api', - 'namespace' => $this->namespace, - 'prefix' => 'api', - ], function ($router) { - foreach (glob(app_path('Http//Routes//Api') . '/*.php') as $file) { - $this->app->make('Pterodactyl\\Http\\Routes\\Api\\' . basename($file, '.php'))->map($router); - } - }); - } } diff --git a/app/Repositories/APIRepository.php b/app/Repositories/APIRepository.php index 95b389ed5..b92a7cab0 100644 --- a/app/Repositories/APIRepository.php +++ b/app/Repositories/APIRepository.php @@ -43,31 +43,35 @@ class APIRepository '*', // User Management Routes - 'api.users', + 'api.users.list', + 'api.users.create', 'api.users.view', - 'api.users.post', + 'api.users.update', 'api.users.delete', - 'api.users.patch', // Server Manaement Routes - 'api.servers', + 'api.servers.list', + 'api.servers.create', 'api.servers.view', - 'api.servers.post', + 'api.servers.config', + 'api.servers.build', 'api.servers.suspend', 'api.servers.unsuspend', 'api.servers.delete', // Node Management Routes - 'api.nodes', - 'api.nodes.view', - 'api.nodes.post', - 'api.nodes.view_allocations', + 'api.nodes.list', + 'api.nodes.create', + 'api.nodes.list', + 'api.nodes.allocations', 'api.nodes.delete', - // Assorted Routes - 'api.services', + // Service Routes + 'api.services.list', 'api.services.view', - 'api.locations', + + // Location Routes + 'api.locations.list', ]; /** diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index a38b4e3a0..c2a230a72 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -96,8 +96,9 @@ class ServerRepository 'io' => 'required|numeric|min:10|max:1000', 'cpu' => 'required|numeric|min:0', 'disk' => 'required|numeric|min:0', - 'ip' => 'required|ip', - 'port' => 'required|numeric|min:1|max:65535', + 'allocation' => 'numeric|exists:allocations,id|required_without:ip,port', + 'ip' => 'required_without:allocation|ip', + 'port' => 'required_without:allocation|numeric|min:1|max:65535', 'service' => 'required|numeric|min:1|exists:services,id', 'option' => 'required|numeric|min:1|exists:service_options,id', 'startup' => 'required', @@ -113,14 +114,20 @@ class ServerRepository // Get the User ID; user exists since we passed the 'exists:users,email' part of the validation $user = Models\User::select('id')->where('email', $data['owner'])->first(); + // Get Node Information + $node = Models\Node::getByID($data['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 - $node = Models\Node::getByID($data['node']); - $allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->where('node', $data['node'])->whereNull('assigned_to')->first(); + if (!isset($data['allocation'])) { + $allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->where('node', $data['node'])->whereNull('assigned_to')->first(); + } else { + $allocation = Models\Allocation::where('id' , $data['allocation'])->where('node', $data['node'])->whereNull('assigned_to')->first(); + } // Something failed in the query, either that combo doesn't exist, or it is in use. if (!$allocation) { - throw new DisplayException('The selected IP/Port combination (' . $data['ip'] . ':' . $data['port'] . ') is either already in use, or unavaliable for this node.'); + throw new DisplayException('The selected IP/Port combination or Allocation ID is either already in use, or unavaliable for this node.'); } // Validate those Service Option Variables @@ -282,7 +289,7 @@ class ServerRepository return $server->id; } catch (\GuzzleHttp\Exception\TransferException $ex) { DB::rollBack(); - throw new DisplayException('An error occured while attempting to create the server.', $ex); + throw new DisplayException('There was an error while attempting to connect to the daemon to add this server.', $ex); } catch (\Exception $ex) { DB::rollBack(); Log:error($ex); @@ -735,6 +742,12 @@ class ServerRepository DB::beginTransaction(); try { + + // Already suspended, no need to make more requests. + if ($server->suspended === 1) { + return true; + } + $server->suspended = 1; $server->save(); @@ -749,7 +762,7 @@ class ServerRepository return DB::commit(); } catch (\GuzzleHttp\Exception\TransferException $ex) { DB::rollBack(); - throw new DisplayException('An error occured while attempting 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) { DB::rollBack(); throw $ex; @@ -769,6 +782,12 @@ class ServerRepository DB::beginTransaction(); try { + + // Already unsuspended, no need to make more requests. + if ($server->suspended === 0) { + return true; + } + $server->suspended = 0; $server->save(); @@ -783,7 +802,7 @@ class ServerRepository return DB::commit(); } catch (\GuzzleHttp\Exception\TransferException $ex) { DB::rollBack(); - throw new DisplayException('An error occured while attempting 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) { DB::rollBack(); throw $ex; diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 92702d59e..47cf78f66 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -49,8 +49,8 @@ class UserRepository /** * Creates a user on the panel. Returns the created user's ID. * - * @param string $email - * @param string|null $password An unhashed version of the user's password. + * @param string $email + * @param string|null $password An unhashed version of the user's password. * @return bool|integer */ public function create($email, $password = null, $admin = false) diff --git a/app/Transformers/AllocationTransformer.php b/app/Transformers/AllocationTransformer.php new file mode 100644 index 000000000..2ab9e5b1f --- /dev/null +++ b/app/Transformers/AllocationTransformer.php @@ -0,0 +1,42 @@ + + * + * 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\Transformers; + +use Pterodactyl\Models\Allocation; +use League\Fractal\TransformerAbstract; + +class AllocationTransformer extends TransformerAbstract +{ + + /** + * Turn this item object into a generic array + * + * @return array + */ + public function transform(Allocation $allocation) + { + return array_except($allocation, ['created_at', 'updated_at']); + } + +} diff --git a/app/Transformers/NodeTransformer.php b/app/Transformers/NodeTransformer.php new file mode 100755 index 000000000..9b3224c37 --- /dev/null +++ b/app/Transformers/NodeTransformer.php @@ -0,0 +1,42 @@ + + * + * 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\Transformers; + +use Pterodactyl\Models\Node; +use League\Fractal\TransformerAbstract; + +class NodeTransformer extends TransformerAbstract +{ + + /** + * Turn this item object into a generic array + * + * @return array + */ + public function transform(Node $node) + { + return $node; + } + +} diff --git a/app/Transformers/ServerTransformer.php b/app/Transformers/ServerTransformer.php new file mode 100755 index 000000000..482cf8419 --- /dev/null +++ b/app/Transformers/ServerTransformer.php @@ -0,0 +1,42 @@ + + * + * 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\Transformers; + +use Pterodactyl\Models\Server; +use League\Fractal\TransformerAbstract; + +class ServerTransformer extends TransformerAbstract +{ + + /** + * Turn this item object into a generic array + * + * @return array + */ + public function transform(Server $server) + { + return $server; + } + +} diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php new file mode 100755 index 000000000..abd90c34d --- /dev/null +++ b/app/Transformers/UserTransformer.php @@ -0,0 +1,42 @@ + + * + * 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\Transformers; + +use Pterodactyl\Models\User; +use League\Fractal\TransformerAbstract; + +class UserTransformer extends TransformerAbstract +{ + + /** + * Turn this item object into a generic array + * + * @return array + */ + public function transform(User $user) + { + return $user; + } + +} diff --git a/config/api.php b/config/api.php new file mode 100644 index 000000000..2a847e7ac --- /dev/null +++ b/config/api.php @@ -0,0 +1,220 @@ + env('API_STANDARDS_TREE', 'x'), + + /* + |-------------------------------------------------------------------------- + | API Subtype + |-------------------------------------------------------------------------- + | + | Your subtype will follow the standards tree you use when used in the + | "Accept" header to negotiate the content type and version. + | + | For example: Accept: application/x.SUBTYPE.v1+json + | + */ + + 'subtype' => env('API_SUBTYPE', ''), + + /* + |-------------------------------------------------------------------------- + | Default API Version + |-------------------------------------------------------------------------- + | + | This is the default version when strict mode is disabled and your API + | is accessed via a web browser. It's also used as the default version + | when generating your APIs documentation. + | + */ + + 'version' => env('API_VERSION', 'v1'), + + /* + |-------------------------------------------------------------------------- + | Default API Prefix + |-------------------------------------------------------------------------- + | + | A default prefix to use for your API routes so you don't have to + | specify it for each group. + | + */ + + 'prefix' => env('API_PREFIX', 'api'), + + /* + |-------------------------------------------------------------------------- + | Default API Domain + |-------------------------------------------------------------------------- + | + | A default domain to use for your API routes so you don't have to + | specify it for each group. + | + */ + + 'domain' => env('API_DOMAIN', null), + + /* + |-------------------------------------------------------------------------- + | Name + |-------------------------------------------------------------------------- + | + | When documenting your API using the API Blueprint syntax you can + | configure a default name to avoid having to manually specify + | one when using the command. + | + */ + + 'name' => env('API_NAME', null), + + /* + |-------------------------------------------------------------------------- + | Conditional Requests + |-------------------------------------------------------------------------- + | + | Globally enable conditional requests so that an ETag header is added to + | any successful response. Subsequent requests will perform a check and + | will return a 304 Not Modified. This can also be enabled or disabled + | on certain groups or routes. + | + */ + + 'conditionalRequest' => env('API_CONDITIONAL_REQUEST', true), + + /* + |-------------------------------------------------------------------------- + | Strict Mode + |-------------------------------------------------------------------------- + | + | Enabling strict mode will require clients to send a valid Accept header + | with every request. This also voids the default API version, meaning + | your API will not be browsable via a web browser. + | + */ + + 'strict' => env('API_STRICT', false), + + /* + |-------------------------------------------------------------------------- + | Debug Mode + |-------------------------------------------------------------------------- + | + | Enabling debug mode will result in error responses caused by thrown + | exceptions to have a "debug" key that will be populated with + | more detailed information on the exception. + | + */ + + 'debug' => env('API_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Generic Error Format + |-------------------------------------------------------------------------- + | + | When some HTTP exceptions are not caught and dealt with the API will + | generate a generic error response in the format provided. Any + | keys that aren't replaced with corresponding values will be + | removed from the final response. + | + */ + + 'errorFormat' => [ + 'message' => ':message', + 'errors' => ':errors', + 'code' => ':code', + 'status_code' => ':status_code', + 'debug' => ':debug', + ], + + /* + |-------------------------------------------------------------------------- + | API Middleware + |-------------------------------------------------------------------------- + | + | Middleware that will be applied globally to all API requests. + | + */ + + 'middleware' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Providers + |-------------------------------------------------------------------------- + | + | The authentication providers that should be used when attempting to + | authenticate an incoming API request. + | + */ + + 'auth' => [ + 'custom' => 'Pterodactyl\Http\Middleware\APISecretToken', + ], + + /* + |-------------------------------------------------------------------------- + | Throttling / Rate Limiting + |-------------------------------------------------------------------------- + | + | Consumers of your API can be limited to the amount of requests they can + | make. You can create your own throttles or simply change the default + | throttles. + | + */ + + 'throttling' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | Response Transformer + |-------------------------------------------------------------------------- + | + | Responses can be transformed so that they are easier to format. By + | default a Fractal transformer will be used to transform any + | responses prior to formatting. You can easily replace + | this with your own transformer. + | + */ + + 'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class), + + /* + |-------------------------------------------------------------------------- + | Response Formats + |-------------------------------------------------------------------------- + | + | Responses can be returned in multiple formats by registering different + | response formatters. You can also customize an existing response + | formatter. + | + */ + + 'defaultFormat' => env('API_DEFAULT_FORMAT', 'json'), + + 'formats' => [ + 'json' => Dingo\Api\Http\Response\Format\Json::class, + ], + +]; diff --git a/config/app.php b/config/app.php index 892ca9090..52d3fa3e4 100644 --- a/config/app.php +++ b/config/app.php @@ -114,6 +114,8 @@ return [ 'providers' => [ + Dingo\Api\Provider\LaravelServiceProvider::class, + /* * Laravel Framework Service Providers... */ diff --git a/resources/views/admin/api/index.blade.php b/resources/views/admin/api/index.blade.php index 3c4edb35c..205b6e88f 100644 --- a/resources/views/admin/api/index.blade.php +++ b/resources/views/admin/api/index.blade.php @@ -58,7 +58,7 @@ {{ $perm->permission }}
@endforeach - {{ $key->created_at }} + {{ (new Carbon($key->created_at))->toDayDateTimeString() }} @endforeach diff --git a/resources/views/admin/api/new.blade.php b/resources/views/admin/api/new.blade.php index a2ae23c6f..4f6343a74 100644 --- a/resources/views/admin/api/new.blade.php +++ b/resources/views/admin/api/new.blade.php @@ -47,31 +47,31 @@

User Management


@@ -80,37 +80,49 @@

Server Management


+
+
+
+
@@ -121,31 +133,31 @@

Node Management


@@ -154,20 +166,20 @@

Service Management


Location Management