Merge pull request #29 from Pterodactyl/add-restful-api
Add initial API Implementation
This commit is contained in:
commit
7670cf1466
47 changed files with 1522 additions and 462 deletions
|
@ -22,3 +22,7 @@ MAIL_PORT=2525
|
|||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
|
||||
API_PREFIX=api
|
||||
API_VERSION=v1
|
||||
API_DEBUG=false
|
||||
|
|
|
@ -55,7 +55,7 @@ class Handler extends ExceptionHandler
|
|||
$e = new NotFoundHttpException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('api/*') || $request->is('remote/*')) {
|
||||
if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('remote/*')) {
|
||||
|
||||
$exception = 'An exception occured while attempting to perform this action, please try again.';
|
||||
|
||||
|
|
11
app/Http/Controllers/API/BaseController.php
Normal file
11
app/Http/Controllers/API/BaseController.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\API;
|
||||
|
||||
use Dingo\Api\Routing\Helpers;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
class BaseController extends Controller
|
||||
{
|
||||
use Helpers;
|
||||
}
|
43
app/Http/Controllers/API/LocationController.php
Normal file
43
app/Http/Controllers/API/LocationController.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
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 getLocations(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;
|
||||
}
|
||||
|
||||
}
|
177
app/Http/Controllers/API/NodeController.php
Normal file
177
app/Http/Controllers/API/NodeController.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\API;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use Pterodactyl\Models;
|
||||
use Pterodactyl\Transformers\NodeTransformer;
|
||||
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 getNodes(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 <jwt-token>"}),
|
||||
* @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 postNode(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 getNode(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 $query->first();
|
||||
} catch (NotFoundHttpException $ex) {
|
||||
throw $ex;
|
||||
} catch (\Exception $ex) {
|
||||
throw new BadRequestHttpException('There was an issue with the fields passed in the request.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List Node Allocations
|
||||
*
|
||||
* Returns a listing of all node allocations.
|
||||
*
|
||||
* @Get("/nodes/{id}/allocations")
|
||||
* @Versions({"v1"})
|
||||
* @Parameters({
|
||||
* @Parameter("id", type="integer", required=true, description="The ID of the node to get allocations for."),
|
||||
* })
|
||||
* @Response(200)
|
||||
*/
|
||||
public function getNodeAllocations(Request $request, $id)
|
||||
{
|
||||
$allocations = Models\Allocation::select('ip', 'port', 'assigned_to')->where('node', $id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get();
|
||||
if ($allocations->count() < 1) {
|
||||
throw new NotFoundHttpException('No allocations where found for the requested node.');
|
||||
}
|
||||
return $allocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Node
|
||||
*
|
||||
* @Delete("/nodes/{id}")
|
||||
* @Versions({"v1"})
|
||||
* @Parameters({
|
||||
* @Parameter("id", type="integer", required=true, description="The ID of the node."),
|
||||
* })
|
||||
* @Response(204)
|
||||
*/
|
||||
public function deleteNode(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.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
179
app/Http/Controllers/API/ServerController.php
Normal file
179
app/Http/Controllers/API/ServerController.php
Normal file
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\API;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
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 getServers(Request $request)
|
||||
{
|
||||
$servers = Models\Server::paginate(50);
|
||||
return $this->response->paginator($servers, new ServerTransformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Server
|
||||
*
|
||||
* @Post("/servers")
|
||||
* @Versions({"v1"})
|
||||
* @Parameters({
|
||||
* @Parameter("page", type="integer", description="The page of results to view.", default=1)
|
||||
* })
|
||||
* @Response(201)
|
||||
*/
|
||||
public function postServer(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 $e) {
|
||||
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 getServer(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.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 postServerSuspend(Request $request, $id)
|
||||
{
|
||||
try {
|
||||
$server = new ServerRepository;
|
||||
$server->suspend($id);
|
||||
} 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 postServerUnsuspend(Request $request, $id)
|
||||
{
|
||||
try {
|
||||
$server = new ServerRepository;
|
||||
$server->unsuspend($id);
|
||||
} 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 deleteServer(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.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
47
app/Http/Controllers/API/ServiceController.php
Normal file
47
app/Http/Controllers/API/ServiceController.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
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 getServices(Request $request)
|
||||
{
|
||||
return Models\Service::all();
|
||||
}
|
||||
|
||||
public function getService(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
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,82 +2,180 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\API;
|
||||
|
||||
use Gate;
|
||||
use Log;
|
||||
use Debugbar;
|
||||
use Pterodactyl\Models\API;
|
||||
use Pterodactyl\Models\User;
|
||||
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserController extends Controller
|
||||
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
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* 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 __construct()
|
||||
public function getUsers(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function getAllUsers(Request $request)
|
||||
{
|
||||
|
||||
// Policies don't work if the user isn't logged in for whatever reason in Laravel...
|
||||
if(!API::checkPermission($request->header('X-Authorization'), 'get-users')) {
|
||||
return API::noPermissionError();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'users' => User::all()
|
||||
]);
|
||||
$users = Models\User::paginate(50);
|
||||
return $this->response->paginator($users, new UserTransformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JSON response about a user given their ID.
|
||||
* If fields are provided only those fields are returned.
|
||||
* List Specific User
|
||||
*
|
||||
* Does not return protected fields (i.e. password & totp_secret)
|
||||
* Lists specific fields about a user or all fields pertaining to that user.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @param string $fields
|
||||
* @return Response
|
||||
* @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 getUser(Request $request, $id, $fields = null)
|
||||
public function getUser(Request $request, $id)
|
||||
{
|
||||
$query = Models\User::where('id', $id);
|
||||
|
||||
// Policies don't work if the user isn't logged in for whatever reason in Laravel...
|
||||
if(!API::checkPermission($request->header('X-Authorization'), 'get-users')) {
|
||||
return API::noPermissionError();
|
||||
if (!is_null($request->input('fields'))) {
|
||||
foreach(explode(',', $request->input('fields')) as $field) {
|
||||
if (!empty($field)) {
|
||||
$query->addSelect($field);
|
||||
}
|
||||
|
||||
if (is_null($fields)) {
|
||||
return response()->json(User::find($id));
|
||||
}
|
||||
|
||||
$query = User::where('id', $id);
|
||||
$explode = explode(',', $fields);
|
||||
|
||||
foreach($explode as &$exploded) {
|
||||
if(!empty($exploded)) {
|
||||
$query->addSelect($exploded);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return response()->json($query->get());
|
||||
} catch (\Exception $e) {
|
||||
if ($e instanceof \Illuminate\Database\QueryException) {
|
||||
return response()->json([
|
||||
'error' => 'One of the fields provided in your argument list is invalid.'
|
||||
], 500);
|
||||
if (!$query->first()) {
|
||||
throw new NotFoundHttpException('No user by that ID was found.');
|
||||
}
|
||||
throw $e;
|
||||
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 <jwt-token>"}),
|
||||
* @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 postUser(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 <jwt-token>"}),
|
||||
* @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 patchUser(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 <jwt-token>"}),
|
||||
* @Response(204),
|
||||
* @Response(422)
|
||||
* })
|
||||
* @Parameters({
|
||||
* @Parameter("id", type="integer", required=true, description="The ID of the user to delete.")
|
||||
* })
|
||||
*/
|
||||
public function deleteUser(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.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,27 +62,18 @@ class AccountsController extends Controller
|
|||
|
||||
public function postNew(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|confirmed|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'
|
||||
]);
|
||||
|
||||
try {
|
||||
$user = new UserRepository;
|
||||
$userid = $user->create($request->input('username'), $request->input('email'), $request->input('password'));
|
||||
|
||||
if (!$userid) {
|
||||
throw new \Exception('Unable to create user, response was not an integer.');
|
||||
}
|
||||
|
||||
Alert::success('Account has been successfully created.')->flash();
|
||||
return redirect()->route('admin.accounts.view', ['id' => $userid]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
} catch (\Pterodactyl\Exceptions\DisplayValidationException $ex) {
|
||||
return redirect()->route('admin.nodes.view', $id)->withErrors(json_decode($e->getMessage()))->withInput();
|
||||
} catch (\Exception $ex) {
|
||||
Log::error($ex);
|
||||
Alert::danger('An error occured while attempting to add a new user. ' . $e->getMessage())->flash();
|
||||
return redirect()->route('admin.accounts.new');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function postUpdate(Request $request)
|
||||
|
|
|
@ -17,7 +17,6 @@ class Kernel extends HttpKernel
|
|||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -30,7 +29,7 @@ class Kernel extends HttpKernel
|
|||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'server' => \Pterodactyl\Http\Middleware\CheckServer::class,
|
||||
'api' => \Pterodactyl\Http\Middleware\APIAuthenticate::class,
|
||||
'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class,
|
||||
'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Debugbar;
|
||||
|
||||
use Pterodactyl\Models\API;
|
||||
|
||||
class APIAuthenticate
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
|
||||
if(!$request->header('X-Authorization')) {
|
||||
return response()->json([
|
||||
'error' => 'Authorization header was missing with this request. Please pass the \'X-Authorization\' header with your request.'
|
||||
], 403);
|
||||
}
|
||||
|
||||
$api = API::where('key', $request->header('X-Authorization'))->first();
|
||||
if (!$api) {
|
||||
return response()->json([
|
||||
'error' => 'Invalid API key was provided in the request.'
|
||||
], 403);
|
||||
}
|
||||
|
||||
if (!is_null($api->allowed_ips)) {
|
||||
if (!in_array($request->ip(), json_decode($api->allowed_ips, true))) {
|
||||
return response()->json([
|
||||
'error' => 'This IP (' . $request->ip() . ') is not permitted to access the API with that token.'
|
||||
], 403);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
|
||||
}
|
||||
}
|
80
app/Http/Middleware/APISecretToken.php
Normal file
80
app/Http/Middleware/APISecretToken.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Middleware;
|
||||
|
||||
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
|
||||
|
||||
class APISecretToken extends Authorization
|
||||
{
|
||||
|
||||
protected $algo = 'sha256';
|
||||
|
||||
protected $permissionAllowed = false;
|
||||
|
||||
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)) {
|
||||
if (!in_array($request->ip(), json_decode($key->allowed_ips))) {
|
||||
throw new AccessDeniedHttpException('This IP address 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.');
|
||||
}
|
||||
}
|
||||
|
||||
if($this->_generateHMAC($request->fullUrl(), $request->getContent(), $key->secret) !== base64_decode($hashed)) {
|
||||
throw new BadRequestHttpException('The hashed body was not valid. Potential modification of contents in route.');
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
protected function _generateHMAC($url, $body, $key)
|
||||
{
|
||||
$data = urldecode($url) . '.' . $body;
|
||||
return hash_hmac($this->algo, $data, $key, true);
|
||||
}
|
||||
|
||||
}
|
129
app/Http/Routes/APIRoutes.php
Normal file
129
app/Http/Routes/APIRoutes.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
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',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@getUsers'
|
||||
]);
|
||||
|
||||
$api->post('users', [
|
||||
'as' => 'api.users.post',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@postUser'
|
||||
]);
|
||||
|
||||
$api->get('users/{id}', [
|
||||
'as' => 'api.users.view',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@getUser'
|
||||
]);
|
||||
|
||||
$api->patch('users/{id}', [
|
||||
'as' => 'api.users.patch',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@patchUser'
|
||||
]);
|
||||
|
||||
$api->delete('users/{id}', [
|
||||
'as' => 'api.users.delete',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\UserController@deleteUser'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Server Routes
|
||||
*/
|
||||
$api->get('servers', [
|
||||
'as' => 'api.servers',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@getServers'
|
||||
]);
|
||||
|
||||
$api->post('servers', [
|
||||
'as' => 'api.servers.post',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@postServer'
|
||||
]);
|
||||
|
||||
$api->get('servers/{id}', [
|
||||
'as' => 'api.servers.view',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@getServer'
|
||||
]);
|
||||
|
||||
$api->post('servers/{id}/suspend', [
|
||||
'as' => 'api.servers.suspend',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@postServerSuspend'
|
||||
]);
|
||||
|
||||
$api->post('servers/{id}/unsuspend', [
|
||||
'as' => 'api.servers.unsuspend',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@postServerUnsuspend'
|
||||
]);
|
||||
|
||||
$api->delete('servers/{id}/{force?}', [
|
||||
'as' => 'api.servers.delete',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@deleteServer'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Node Routes
|
||||
*/
|
||||
$api->get('nodes', [
|
||||
'as' => 'api.nodes',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@getNodes'
|
||||
]);
|
||||
|
||||
$api->post('nodes', [
|
||||
'as' => 'api.nodes.post',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@postNode'
|
||||
]);
|
||||
|
||||
$api->get('nodes/{id}', [
|
||||
'as' => 'api.nodes.view',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@getNode'
|
||||
]);
|
||||
|
||||
$api->get('nodes/{id}/allocations', [
|
||||
'as' => 'api.nodes.view_allocations',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@getNodeAllocations'
|
||||
]);
|
||||
|
||||
$api->delete('nodes/{id}', [
|
||||
'as' => 'api.nodes.view',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@deleteNode'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Location Routes
|
||||
*/
|
||||
$api->get('locations', [
|
||||
'as' => 'api.locations',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\LocationController@getLocations'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Service Routes
|
||||
*/
|
||||
$api->get('services', [
|
||||
'as' => 'api.services',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@getServices'
|
||||
]);
|
||||
|
||||
$api->get('services/{id}', [
|
||||
'as' => 'api.services.view',
|
||||
'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@getService'
|
||||
]);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -13,7 +13,8 @@ class AdminRoutes {
|
|||
'as' => 'admin.index',
|
||||
'middleware' => [
|
||||
'auth',
|
||||
'admin'
|
||||
'admin',
|
||||
'csrf'
|
||||
],
|
||||
'uses' => 'Admin\BaseController@getIndex'
|
||||
]);
|
||||
|
@ -22,7 +23,8 @@ class AdminRoutes {
|
|||
'prefix' => 'admin/accounts',
|
||||
'middleware' => [
|
||||
'auth',
|
||||
'admin'
|
||||
'admin',
|
||||
'csrf'
|
||||
]
|
||||
], function () use ($router) {
|
||||
|
||||
|
@ -66,7 +68,8 @@ class AdminRoutes {
|
|||
'prefix' => 'admin/servers',
|
||||
'middleware' => [
|
||||
'auth',
|
||||
'admin'
|
||||
'admin',
|
||||
'csrf'
|
||||
]
|
||||
], function () use ($router) {
|
||||
|
||||
|
@ -148,7 +151,8 @@ class AdminRoutes {
|
|||
'prefix' => 'admin/nodes',
|
||||
'middleware' => [
|
||||
'auth',
|
||||
'admin'
|
||||
'admin',
|
||||
'csrf'
|
||||
]
|
||||
], function () use ($router) {
|
||||
|
||||
|
@ -204,7 +208,8 @@ class AdminRoutes {
|
|||
'prefix' => 'admin/locations',
|
||||
'middleware' => [
|
||||
'auth',
|
||||
'admin'
|
||||
'admin',
|
||||
'csrf'
|
||||
]
|
||||
], function () use ($router) {
|
||||
$router->get('/', [
|
||||
|
|
|
@ -12,7 +12,8 @@ class AuthRoutes {
|
|||
$router->group([
|
||||
'prefix' => 'auth',
|
||||
'middleware' => [
|
||||
'guest'
|
||||
'guest',
|
||||
'csrf'
|
||||
]
|
||||
], function () use ($router) {
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ class BaseRoutes {
|
|||
$router->group([
|
||||
'profix' => 'account',
|
||||
'middleware' => [
|
||||
'auth'
|
||||
'auth',
|
||||
'csrf'
|
||||
]
|
||||
], function () use ($router) {
|
||||
$router->get('account', [
|
||||
|
@ -50,7 +51,8 @@ class BaseRoutes {
|
|||
$router->group([
|
||||
'prefix' => 'account/totp',
|
||||
'middleware' => [
|
||||
'auth'
|
||||
'auth',
|
||||
'csrf'
|
||||
]
|
||||
], function () use ($router) {
|
||||
$router->get('/', [
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Routes;
|
||||
|
||||
use Illuminate\Routing\Router;
|
||||
|
||||
class RestRoutes {
|
||||
|
||||
public function map(Router $router) {
|
||||
$router->group([
|
||||
'prefix' => 'api/v1',
|
||||
'middleware' => [
|
||||
'api'
|
||||
]
|
||||
], function () use ($router) {
|
||||
// Users endpoint for API
|
||||
$router->group(['prefix' => 'users'], function () use ($router) {
|
||||
// Returns all users
|
||||
$router->get('/', [
|
||||
'uses' => 'API\UserController@getAllUsers'
|
||||
]);
|
||||
|
||||
// Return listing of user [with only specified fields]
|
||||
$router->get('/{id}/{fields?}', [
|
||||
'uses' => 'API\UserController@getUser'
|
||||
])->where('id', '[0-9]+');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -11,7 +11,8 @@ class ServerRoutes {
|
|||
'prefix' => 'server/{server}',
|
||||
'middleware' => [
|
||||
'auth',
|
||||
'server'
|
||||
'server',
|
||||
'csrf'
|
||||
]
|
||||
], function ($server) use ($router) {
|
||||
// Index View for Server
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
use Log;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
use Pterodactyl\Models\APIPermission;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class API extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'api';
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = ['daemonSecret'];
|
||||
|
||||
public function permissions()
|
||||
{
|
||||
return $this->hasMany(APIPermission::class);
|
||||
}
|
||||
|
||||
public static function findKey($key)
|
||||
{
|
||||
return self::where('key', $key)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an API key has permission to perform an action.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $permission
|
||||
* @return boolean
|
||||
*/
|
||||
public static function checkPermission($key, $permission)
|
||||
{
|
||||
$api = self::findKey($key);
|
||||
|
||||
if (!$api) {
|
||||
throw new DisplayException('The requested API key (' . $key . ') was not found in the system.');
|
||||
}
|
||||
|
||||
return APIPermission::check($api->id, $permission);
|
||||
|
||||
}
|
||||
|
||||
public static function noPermissionError($error = 'You do not have permission to perform this action with this API key.')
|
||||
{
|
||||
return response()->json([
|
||||
'error' => 'You do not have permission to perform this action with this API key.'
|
||||
], 403);
|
||||
}
|
||||
|
||||
}
|
17
app/Models/APIKey.php
Normal file
17
app/Models/APIKey.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class APIKey extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'api_keys';
|
||||
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
use Debugbar;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class APIPermission extends Model
|
||||
|
@ -15,16 +14,4 @@ class APIPermission extends Model
|
|||
*/
|
||||
protected $table = 'api_permissions';
|
||||
|
||||
/**
|
||||
* Checks if an API key has a specific permission.
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $permission
|
||||
* @return boolean
|
||||
*/
|
||||
public static function check($id, $permission)
|
||||
{
|
||||
return self::where('key_id', $id)->where('permission', $permission)->exists();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class User extends Model implements AuthenticatableContract,
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name', 'email', 'password'];
|
||||
protected $fillable = ['name', 'email', 'password', 'use_totp', 'totp_secret', 'language'];
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
|
|
|
@ -183,4 +183,10 @@ class NodeRepository {
|
|||
}
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
// @TODO: add logic;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -685,4 +685,28 @@ class ServerRepository
|
|||
return $server->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspends a server instance making it unable to be booted or used by a user.
|
||||
* @param integer $id
|
||||
* @return boolean
|
||||
*/
|
||||
public function suspend($id)
|
||||
{
|
||||
// @TODO: Implement logic; not doing it now since that is outside of the
|
||||
// scope of this API brance.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsuspends a server instance.
|
||||
* @param integer $id
|
||||
* @return boolean
|
||||
*/
|
||||
public function unsuspend($id)
|
||||
{
|
||||
// @TODO: Implement logic; not doing it now since that is outside of the
|
||||
// scope of this API brance.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,11 +2,16 @@
|
|||
|
||||
namespace Pterodactyl\Repositories;
|
||||
|
||||
use DB;
|
||||
use Hash;
|
||||
use Validator;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models;
|
||||
use Pterodactyl\Services\UuidService;
|
||||
|
||||
use Pterodactyl\Exceptions\DisplayValidationException;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
|
||||
class UserRepository
|
||||
{
|
||||
|
||||
|
@ -22,32 +27,70 @@ class UserRepository
|
|||
* @param string $password An unhashed version of the user's password.
|
||||
* @return bool|integer
|
||||
*/
|
||||
public function create($email, $password)
|
||||
public function create($email, $password, $admin = false)
|
||||
{
|
||||
$user = new User;
|
||||
$validator = Validator::make([
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'root_admin' => $admin
|
||||
], [
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
|
||||
'root_admin' => 'required|boolean'
|
||||
]);
|
||||
|
||||
// Run validator, throw catchable and displayable exception if it fails.
|
||||
// Exception includes a JSON result of failed validation rules.
|
||||
if ($validator->fails()) {
|
||||
throw new DisplayValidationException($validator->errors());
|
||||
}
|
||||
|
||||
$user = new Models\User;
|
||||
$uuid = new UuidService;
|
||||
|
||||
$user->uuid = $uuid->generate('users', 'uuid');
|
||||
$user->email = $email;
|
||||
$user->password = Hash::make($password);
|
||||
$user->language = 'en';
|
||||
$user->root_admin = ($admin) ? 1 : 0;
|
||||
|
||||
return ($user->save()) ? $user->id : false;
|
||||
try {
|
||||
$user->save();
|
||||
return $user->id;
|
||||
} catch (\Exception $ex) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a user on the panel.
|
||||
*
|
||||
* @param integer $id
|
||||
* @param array $user An array of columns and their associated values to update for the user.
|
||||
* @param array $data An array of columns and their associated values to update for the user.
|
||||
* @return boolean
|
||||
*/
|
||||
public function update($id, array $user)
|
||||
public function update($id, array $data)
|
||||
{
|
||||
if(array_key_exists('password', $user)) {
|
||||
$user['password'] = Hash::make($user['password']);
|
||||
$validator = Validator::make($data, [
|
||||
'email' => 'email|unique:users,email,' . $id,
|
||||
'password' => 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
|
||||
'root_admin' => 'boolean',
|
||||
'language' => 'string|min:1|max:5',
|
||||
'use_totp' => 'boolean',
|
||||
'totp_secret' => 'size:16'
|
||||
]);
|
||||
|
||||
// Run validator, throw catchable and displayable exception if it fails.
|
||||
// Exception includes a JSON result of failed validation rules.
|
||||
if ($validator->fails()) {
|
||||
throw new DisplayValidationException($validator->errors());
|
||||
}
|
||||
|
||||
return User::find($id)->update($user);
|
||||
if(array_key_exists('password', $data)) {
|
||||
$user['password'] = Hash::make($data['password']);
|
||||
}
|
||||
|
||||
return Models\User::findOrFail($id)->update($data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,7 +101,22 @@ class UserRepository
|
|||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
return User::destroy($id);
|
||||
if(Models\Server::where('owner', $id)->count() > 0) {
|
||||
throw new DisplayException('Cannot delete a user with active servers attached to thier account.');
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
Models\Permission::where('user_id', $id)->delete();
|
||||
Models\Subuser::where('user_id', $id)->delete();
|
||||
Models\User::destroy($id);
|
||||
|
||||
try {
|
||||
DB::commit();
|
||||
return true;
|
||||
} catch (\Exception $ex) {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
21
app/Transformers/NodeTransformer.php
Normal file
21
app/Transformers/NodeTransformer.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
21
app/Transformers/ServerTransformer.php
Normal file
21
app/Transformers/ServerTransformer.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
21
app/Transformers/UserTransformer.php
Normal file
21
app/Transformers/UserTransformer.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
"php": ">=5.5.9",
|
||||
"laravel/framework": "5.2.*",
|
||||
"barryvdh/laravel-debugbar": "^2.0",
|
||||
"dingo/api": "1.0.*@dev",
|
||||
"doctrine/dbal": "^2.5",
|
||||
"guzzlehttp/guzzle": "^6.1",
|
||||
"pragmarx/google2fa": "^0.7.1",
|
||||
|
|
209
config/api.php
Normal file
209
config/api.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Standards Tree
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Versioning an API with Dingo revolves around content negotiation and
|
||||
| custom MIME types. A custom type will belong to one of three
|
||||
| standards trees, the Vendor tree (vnd), the Personal tree
|
||||
| (prs), and the Unregistered tree (x).
|
||||
|
|
||||
| By default the Unregistered tree (x) is used, however, should you wish
|
||||
| to you can register your type with the IANA. For more details:
|
||||
| https://tools.ietf.org/html/rfc6838
|
||||
|
|
||||
*/
|
||||
|
||||
'standardsTree' => 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', 'pterodactyl'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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', null),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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', 'Pterodactyl Panel API'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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,
|
||||
|
||||
],
|
||||
|
||||
];
|
|
@ -112,6 +112,8 @@ return [
|
|||
|
||||
'providers' => [
|
||||
|
||||
Dingo\Api\Provider\LaravelServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Laravel Framework Service Providers...
|
||||
*/
|
||||
|
@ -179,6 +181,8 @@ return [
|
|||
'Crypt' => Illuminate\Support\Facades\Crypt::class,
|
||||
'DB' => Illuminate\Support\Facades\DB::class,
|
||||
'Debugbar' => Barryvdh\Debugbar\Facade::class,
|
||||
'DingoAPI' => Dingo\Api\Facade\API::class,
|
||||
'DingoRoute' => Dingo\Api\Facade\Route::class,
|
||||
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
|
||||
'Event' => Illuminate\Support\Facades\Event::class,
|
||||
'File' => Illuminate\Support\Facades\File::class,
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateTableApiKeys extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('api_keys', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->char('public', 16);
|
||||
$table->char('secret', 32);
|
||||
$table->json('allowed_ips')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('api_keys');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateTableApiPermissions extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('api_permissions', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('key_id')->unsigned();
|
||||
$table->string('permission');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('api_permissions');
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue