diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index a4e40febb..b4c2b507b 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -29,6 +29,7 @@ use Log; use Pterodactyl\Models; use Pterodactyl\Repositories\ServerRepository; +use Pterodactyl\Repositories\DatabaseRepository; use Pterodactly\Exceptions\DisplayException; use Pterodactly\Exceptions\DisplayValidationException; @@ -95,7 +96,12 @@ class ServersController extends Controller ->join('server_variables', 'server_variables.variable_id', '=', 'service_variables.id') ->where('service_variables.option_id', $server->option) ->where('server_variables.server_id', $server->id) - ->get() + ->get(), + 'databases' => Models\Database::select('databases.*', 'database_servers.host as a_host', 'database_servers.port as a_port') + ->where('server', $server->id) + ->join('database_servers', 'database_servers.id', '=', 'databases.db_server') + ->get(), + 'db_servers' => Models\DatabaseServer::all() ]); } @@ -375,4 +381,42 @@ class ServersController extends Controller } } + public function postDatabase(Request $request, $id) + { + try { + $repo = new DatabaseRepository; + $repo->create($id, $request->except([ + '_token' + ])); + Alert::success('Added new database to this server.')->flash(); + } catch (\Pterodactyl\Exceptions\DisplayValidationException $ex) { + return redirect()->route('admin.servers.view', [ + 'id' => $id, + 'tab' => 'tab_database' + ])->withInput()->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An exception occured while attempting to add a new database for this server.')->flash(); + } + + return redirect()->route('admin.servers.view', [ + 'id' => $id, + 'tab' => 'tab_database' + ])->withInput(); + } + + public function deleteDatabase(Request $request, $id, $database) + { + try { + $repo = new DatabaseRepository; + $repo->drop($database); + return response('', 204); + } catch (\Exception $ex) { + Log::error($ex); + return response()->json([ + 'error' => 'An exception occured while attempting to delete this database.' + ], 500); + } + } + } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 120db974d..3164e5c14 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -152,6 +152,16 @@ class AdminRoutes { 'uses' => 'Admin\ServersController@getView' ]); + // Database Stuffs + $router->post('/view/{id}/database', [ + 'as' => 'admin.servers.database', + 'uses' => 'Admin\ServersController@postDatabase' + ]); + + $router->delete('/view/{id}/database/{database}', [ + 'uses' => 'Admin\ServersController@deleteDatabase' + ]); + // Change Server Details $router->post('/view/{id}/details', [ 'uses' => 'Admin\ServersController@postUpdateServerDetails' diff --git a/app/Models/Database.php b/app/Models/Database.php new file mode 100644 index 000000000..c682b296e --- /dev/null +++ b/app/Models/Database.php @@ -0,0 +1,62 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +namespace Pterodactyl\Models; + +use Illuminate\Database\Eloquent\Model; + +class Database extends Model +{ + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'databases'; + + /** + * The attributes excluded from the model's JSON form. + * + * @var array + */ + protected $hidden = ['password']; + + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'server' => 'integer', + 'db_server' => 'integer', + ]; + +} diff --git a/app/Models/DatabaseServer.php b/app/Models/DatabaseServer.php new file mode 100644 index 000000000..2a58e3168 --- /dev/null +++ b/app/Models/DatabaseServer.php @@ -0,0 +1,61 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +namespace Pterodactyl\Models; + +use Illuminate\Database\Eloquent\Model; + +class DatabaseServer extends Model +{ + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'database_servers'; + + /** + * The attributes excluded from the model's JSON form. + * + * @var array + */ + protected $hidden = ['password']; + + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'id' => 'integer', + ]; + +} diff --git a/app/Repositories/DatabaseRepository.php b/app/Repositories/DatabaseRepository.php new file mode 100644 index 000000000..e911a02a5 --- /dev/null +++ b/app/Repositories/DatabaseRepository.php @@ -0,0 +1,146 @@ + + * + * 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\Repositories; + +use Crypt; +use DB; +use Validator; + +use Pterodactyl\Models; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +use Illuminate\Database\Capsule\Manager as Capsule; + +class DatabaseRepository { + + /** + * Adds a new database to a given database server. + * @param int $server Id of the server to add a database for. + * @param array $options Array of options for creating that database. + * @return void + */ + public function create($server, $options) + { + $server = Models\Server::findOrFail($server); + $validator = Validator::make($options, [ + 'db_server' => 'required|exists:database_servers,id', + 'database' => 'required|regex:/^\w{1,100}$/', + 'remote' => 'required|regex:/^[0-9%.]{1,15}$/', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + DB::beginTransaction(); + + $db = new Models\Database; + $db->fill([ + 'server' => $server->id, + 'db_server' => $options['db_server'], + 'database' => $server->uuidShort . '_' . $options['database'], + 'username' => $server->uuidShort . '_' . str_random(7), + 'remote' => $options['remote'], + 'password' => Crypt::encrypt(str_random(20)) + ]); + $db->save(); + + // Contact Remote + $dbr = Models\DatabaseServer::findOrFail($options['db_server']); + + try { + + $capsule = new Capsule; + $capsule->addConnection([ + 'driver' => 'mysql', + 'host' => $dbr->host, + 'port' => $dbr->port, + 'database' => 'mysql', + 'username' => $dbr->username, + 'password' => Crypt::decrypt($dbr->password), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '' + ]); + + $capsule->setAsGlobal(); + + Capsule::statement('CREATE DATABASE ' . $db->database); + Capsule::statement('CREATE USER \'' . $db->username . '\'@\'' . $db->remote . '\' IDENTIFIED BY \'' . Crypt::decrypt($db->password) . '\''); + Capsule::statement('GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON ' . $db->database . '.* TO \'' . $db->username . '\'@\'' . $db->remote . '\''); + Capsule::statement('FLUSH PRIVILEGES'); + + DB::commit(); + return true; + } catch (\Exception $ex) { + DB::rollback(); + throw $ex; + } + } + + /** + * Drops a database from the associated MySQL Server + * @param int $database The ID of the database to drop. + * @return boolean + */ + public function drop($database) + { + $db = Models\Database::findOrFail($database); + $dbr = Models\DatabaseServer::findOrFail($db->db_server); + + try { + + DB::beginTransaction(); + + $capsule = new Capsule; + $capsule->addConnection([ + 'driver' => 'mysql', + 'host' => $dbr->host, + 'port' => $dbr->port, + 'database' => 'mysql', + 'username' => $dbr->username, + 'password' => Crypt::decrypt($dbr->password), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '' + ]); + + $capsule->setAsGlobal(); + + Capsule::statement('DROP USER \'' . $db->username . '\'@\'' . $db->remote . '\''); + Capsule::statement('DROP DATABASE ' . $db->database); + + $db->delete(); + + DB::commit(); + return true; + } catch (\Exception $ex) { + DB::rollback(); + throw $ex; + } + + } + +} diff --git a/database/migrations/2016_02_07_172148_add_databases_tables.php b/database/migrations/2016_02_07_172148_add_databases_tables.php new file mode 100644 index 000000000..9a36a690a --- /dev/null +++ b/database/migrations/2016_02_07_172148_add_databases_tables.php @@ -0,0 +1,36 @@ +increments('id'); + $table->integer('server')->unsigned(); + $table->integer('db_server')->unsigned(); + $table->string('database')->unique(); + $table->string('username')->unique(); + $table->string('remote')->default('%'); + $table->text('password'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('databases'); + } +} diff --git a/database/migrations/2016_02_07_181319_add_database_servers_table.php b/database/migrations/2016_02_07_181319_add_database_servers_table.php new file mode 100644 index 000000000..9d6da726e --- /dev/null +++ b/database/migrations/2016_02_07_181319_add_database_servers_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->string('name'); + $table->string('host'); + $table->integer('port')->unsigned(); + $table->string('username'); + $table->text('password'); + $table->integer('max_databases')->unsigned()->nullable(); + $table->integer('linked_node')->unsigned()->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('database_servers'); + } +} diff --git a/resources/views/admin/servers/view.blade.php b/resources/views/admin/servers/view.blade.php index 3284844ab..1e158e9bf 100644 --- a/resources/views/admin/servers/view.blade.php +++ b/resources/views/admin/servers/view.blade.php @@ -45,6 +45,7 @@