From 22b0bbf6ce6a0ac855e051239e0e83090185dbc0 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 13 Dec 2015 22:22:16 -0500 Subject: [PATCH] Model fixing, moving things around to improve code. Adds unique UUID generator, moves functions into repositories for adding servers and users, cleans up code, adding more comments. --- .../Controllers/Admin/AccountsController.php | 25 ++-- .../Controllers/Admin/ServersController.php | 4 +- app/Http/Controllers/Base/IndexController.php | 100 ++++++-------- app/Models/Server.php | 108 +--------------- app/Models/User.php | 79 ++---------- app/Repositories/ServerRepository.php | 122 ++++++++++++++++++ app/Repositories/UserRepository.php | 44 +++++++ app/Services/UuidService.php | 101 +++++++++++++++ 8 files changed, 333 insertions(+), 250 deletions(-) create mode 100644 app/Repositories/ServerRepository.php create mode 100644 app/Repositories/UserRepository.php create mode 100644 app/Services/UuidService.php diff --git a/app/Http/Controllers/Admin/AccountsController.php b/app/Http/Controllers/Admin/AccountsController.php index 55af688aa..a4b071fa6 100644 --- a/app/Http/Controllers/Admin/AccountsController.php +++ b/app/Http/Controllers/Admin/AccountsController.php @@ -3,11 +3,9 @@ namespace Pterodactyl\Http\Controllers\Admin; use Alert; -use Debugbar; -use Hash; -use Uuid; - use Pterodactyl\Models\User; +use Pterodactyl\Repositories\UserRepository; + use Pterodactyl\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -52,18 +50,19 @@ class AccountsController extends Controller 'password_confirmation' => 'required' ]); - //@TODO: re-generate UUID if conflict - $user = new User; - $user->uuid = Uuid::generate(4); + try { - $user->username = $request->input('username'); - $user->email = $request->input('email'); - $user->password = Hash::make($request->input('password')); + $user = new UserRepository; + $userid = $user->create($request->input('username'), $request->input('email'), $request->input('password')); - $user->save(); + Alert::success('Account has been successfully created.')->flash(); + return redirect()->route('admin.accounts.view', ['id' => $userid]); + + } catch (\Exception $e) { + Alert::danger('An error occured while attempting to add a new user. Please check the logs or try again.')->flash(); + return redirect()->route('admin.accounts.new'); + } - Alert::success('Account has been successfully created.')->flash(); - return redirect()->route('admin.accounts.view', ['id' => $user->id]); } } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 00dd918f4..c307f0f3d 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Controllers\Admin; use Debugbar; +use Pterodactyl\Repositories\ServerRepository; use Pterodactyl\Models\Server; use Pterodactyl\Models\Node; use Pterodactyl\Models\Location; @@ -56,7 +57,8 @@ class ServersController extends Controller { try { - $resp = Server::addServer($request->all()); + $server = new ServerRepository; + $resp = $server->create($request->all()); echo $resp . '
'; } catch (\Exception $e) { Debugbar::addException($e); diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index b8599c517..d62af98cd 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -3,14 +3,12 @@ namespace Pterodactyl\Http\Controllers\Base; use Auth; -use Debugbar; +use Hash; use Google2FA; -use Log; use Alert; -use Pterodactyl\Exceptions\AccountNotFoundException; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Models\User; + use Pterodactyl\Models\Server; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -74,22 +72,18 @@ class IndexController extends Controller public function putAccountTotp(Request $request) { - try { - $totpSecret = User::setTotpSecret(Auth::user()->id); - } catch (\Exception $e) { - if ($e instanceof AccountNotFoundException) { - return response($e->getMessage(), 500); - } - throw $e; - } + $user = $request->user(); + + $user->totp_secret = Google2FA::generateSecretKey(); + $user->save(); return response()->json([ 'qrImage' => Google2FA::getQRCodeGoogleUrl( 'Pterodactyl', - Auth::user()->email, - $totpSecret + $user->email, + $user->totp_secret ), - 'secret' => $totpSecret + 'secret' => $user->totp_secret ]); } @@ -104,21 +98,16 @@ class IndexController extends Controller { if (!$request->has('token')) { - return response('No input \'token\' defined.', 500); + return response(null, 500); } - try { - if(User::toggleTotp(Auth::user()->id, $request->input('token'))) { - return response('true'); - } - return response('false'); - } catch (\Exception $e) { - if ($e instanceof AccountNotFoundException) { - return response($e->getMessage(), 500); - } - throw $e; + $user = $request->user(); + if($user->toggleTotp($request->input('token'))) { + return response('true'); } + return response('false'); + } /** @@ -135,21 +124,14 @@ class IndexController extends Controller return redirect()->route('account.totp'); } - try { - if(User::toggleTotp(Auth::user()->id, $request->input('token'))) { - return redirect()->route('account.totp'); - } - - Alert::danger('Unable to disable TOTP on this account, was the token correct?')->flash(); + $user = $request->user(); + if($user->toggleTotp($request->input('token'))) { return redirect()->route('account.totp'); - } catch (\Exception $e) { - if ($e instanceof AccountNotFoundException) { - Alert::danger('An error occured while attempting to perform this action.')->flash(); - return redirect()->route('account.totp'); - } - throw $e; } + Alert::danger('The TOTP token provided was invalid.')->flash(); + return redirect()->route('account.totp'); + } /** @@ -177,23 +159,19 @@ class IndexController extends Controller 'password' => 'required' ]); - if (!password_verify($request->input('password'), Auth::user()->password)) { + $user = $request->user(); + + if (!password_verify($request->input('password'), $user->password)) { Alert::danger('The password provided was not valid for this account.')->flash(); return redirect()->route('account'); } - // Met Validation, lets roll out. - try { - User::setEmail(Auth::user()->id, $request->input('new_email')); - Alert::success('Your email address has successfully been updated.')->flash(); - return redirect()->route('account'); - } catch (\Exception $e) { - if ($e instanceof AccountNotFoundException || $e instanceof DisplayException) { - Alert::danger($e->getMessage())->flash(); - return redirect()->route('account'); - } - throw $e; - } + $user->email = $request->input('new_email'); + $user->save(); + + Alert::success('Your email address has successfully been updated.')->flash(); + return redirect()->route('account'); + } /** @@ -211,24 +189,22 @@ class IndexController extends Controller 'new_password_confirmation' => 'required' ]); - if (!password_verify($request->input('current_password'), Auth::user()->password)) { + $user = $request->user(); + + if (!password_verify($request->input('current_password'), $user->password)) { Alert::danger('The password provided was not valid for this account.')->flash(); return redirect()->route('account'); } - // Met Validation, lets roll out. try { - User::setPassword(Auth::user()->id, $request->input('new_password')); + $user->setPassword($request->input('new_password')); Alert::success('Your password has successfully been updated.')->flash(); - return redirect()->route('account'); - } catch (\Exception $e) { - if ($e instanceof AccountNotFoundException || $e instanceof DisplayException) { - Alert::danger($e->getMessage())->flash(); - return redirect()->route('account'); - } - throw $e; + } catch (DisplayException $e) { + Alert::danger($e->getMessage())->flash(); } + return redirect()->route('account'); + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 3b0313e63..2fa7bb735 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -3,16 +3,8 @@ namespace Pterodactyl\Models; use Auth; -use DB; -use Debugbar; -use Validator; - -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\AccountNotFoundException; -use Pterodactyl\Exceptions\DisplayValidationException; - -use Pterodactyl\Models; +use Pterodactyl\Models\Subuser; use Illuminate\Database\Eloquent\Model; class Server extends Model @@ -50,18 +42,6 @@ class Server extends Model self::$user = Auth::user(); } - protected static function generateSFTPUsername($name) - { - - $name = preg_replace('/\s+/', '', $name); - if (strlen($name) > 6) { - return strtolower('ptdl-' . substr($name, 0, 6) . '_' . str_random(5)); - } - - return strtolower('ptdl-' . $name . '_' . str_random((11 - strlen($name)))); - - } - /** * Determine if we need to change the server's daemonSecret value to * match that of the user if they are a subuser. @@ -76,7 +56,7 @@ class Server extends Model return $server->daemonSecret; } - $subuser = Models\Subuser::where('server_id', $server->id)->where('user_id', self::$user->id)->first(); + $subuser = Subuser::where('server_id', $server->id)->where('user_id', self::$user->id)->first(); if (is_null($subuser)) { return null; @@ -101,7 +81,7 @@ class Server extends Model ->where('active', 1); if (self::$user->root_admin !== 1) { - $query->whereIn('servers.id', Models\Subuser::accessServers()); + $query->whereIn('servers.id', Subuser::accessServers()); } return $query->get(); @@ -124,7 +104,7 @@ class Server extends Model $query = self::where('uuidShort', $uuid)->where('active', 1); if (self::$user->root_admin !== 1) { - $query->whereIn('servers.id', Models\Subuser::accessServers()); + $query->whereIn('servers.id', Subuser::accessServers()); } $result = $query->first(); @@ -158,84 +138,4 @@ class Server extends Model } - /** - * Adds a new server to the system. - * @param array $data An array of data descriptors for creating the server. These should align to the columns in the database. - */ - public static function addServer(array $data) - { - - // Validate Fields - $validator = Validator::make($data, [ - 'owner' => 'required|email|exists:users,email', - 'node' => 'required|numeric|min:1|exists:nodes,id', - 'name' => 'required|regex:([\w -]{4,35})', - 'memory' => 'required|numeric|min:1', - 'disk' => 'required|numeric|min:1', - 'cpu' => 'required|numeric|min:0', - 'io' => 'required|numeric|min:10|max:1000', - 'ip' => 'required|ip', - 'port' => 'required|numeric|min:1|max:65535', - 'service' => 'required|numeric|min:1|exists:services,id', - 'option' => 'required|numeric|min:1|exists:service_options,id', - 'custom_image_name' => 'required_if:use_custom_image,on', - ]); - - // 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(json_encode($validator->errors()->all())); - } - - // 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(); - - // 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::find($data['node']); - $allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->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.'); - } - - // Validate those Service Option Variables - // We know the service and option exists because of the validation. - // We need to verify that the option exists for the service, and then check for - // any required variable fields. (fields are labeled env_) - $option = Models\ServiceOptions::where('id', $data['option'])->where('parent_service', $data['service'])->first(); - if (!$option) { - throw new DisplayException('The requested service option does not exist for the specified service.'); - } - - // Check those Variables - $variables = Models\ServiceVariables::where('option_id', $data['option'])->get(); - if ($variables) { - foreach($variables as $variable) { - - // Is the variable required? - if (!$data['env_' . $variable->env_variable]) { - if ($variable->required === 1) { - throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.'); - } - - $data['env_' . $variable->env_variable] = $variable->default_value; - continue; - } - - // Check aganist Regex Pattern - if (!is_null($variable->regex) && !preg_match($variable->regex, $data['env_' . $variable->env_variable])) { - throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').'); - } - - continue; - - } - } - - return self::generateSFTPUsername($data['name']); - - } - } diff --git a/app/Models/User.php b/app/Models/User.php index 2b9dc9f4e..df2aa8afe 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Models; +use Hash; use Google2FA; use Pterodactyl\Exceptions\AccountNotFoundException; use Pterodactyl\Exceptions\DisplayException; @@ -47,51 +48,21 @@ class User extends Model implements AuthenticatableContract, return $this->hasMany(Permission::class); } - /** - * Sets the TOTP secret for an account. - * - * @param int $id Account ID for which we want to generate a TOTP secret - * @return string - */ - public static function setTotpSecret($id) - { - - $totpSecretKey = Google2FA::generateSecretKey(); - - $user = User::find($id); - - if (!$user) { - throw new AccountNotFoundException('An account with that ID (' . $id . ') does not exist in the system.'); - } - - $user->totp_secret = $totpSecretKey; - $user->save(); - - return $totpSecretKey; - - } - /** * Enables or disables TOTP on an account if the token is valid. * - * @param int $id Account ID for which we want to generate a TOTP secret + * @param int $token The token that we want to verify. * @return boolean */ - public static function toggleTotp($id, $token) + public function toggleTotp($token) { - $user = User::find($id); - - if (!$user) { - throw new AccountNotFoundException('An account with that ID (' . $id . ') does not exist in the system.'); - } - - if (!Google2FA::verifyKey($user->totp_secret, $token)) { + if (!Google2FA::verifyKey($this->totp_secret, $token)) { return false; } - $user->use_totp = ($user->use_totp === 1) ? 0 : 1; - $user->save(); + $this->use_totp = !$this->use_totp; + $this->save(); return true; @@ -104,51 +75,19 @@ class User extends Model implements AuthenticatableContract, * - at least one lowercase character * - at least one number * - * @param int $id The ID of the account to update the password on. * @param string $password The raw password to set the account password to. * @param string $regex The regex to use when validating the password. Defaults to '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'. * @return void */ - public static function setPassword($id, $password, $regex = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})') + public function setPassword($password, $regex = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})') { - $user = User::find($id); - if (!$user) { - throw new AccountNotFoundException('An account with that ID (' . $id . ') does not exist in the system.'); - } - if (!preg_match($regex, $password)) { throw new DisplayException('The password passed did not meet the minimum password requirements.'); } - $user->password = password_hash($password, PASSWORD_BCRYPT); - $user->save(); - - return; - - } - - /** - * Updates the email address for an account. - * - * @param int $id - * @param string $email - * @return void - */ - public static function setEmail($id, $email) - { - - $user = User::find($id); - if (!$user) { - throw new AccountNotFoundException('An account with that ID (' . $id . ') does not exist in the system.'); - } - - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - throw new DisplayException('The email provided (' . $email . ') was not valid.'); - } - - $user->email = $email; - $user->save(); + $this->password = Hash::make($password); + $this->save(); return; diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php new file mode 100644 index 000000000..d0a00f793 --- /dev/null +++ b/app/Repositories/ServerRepository.php @@ -0,0 +1,122 @@ + 6) { + return strtolower('ptdl-' . substr($name, 0, 6) . '_' . str_random(5)); + } + + return strtolower('ptdl-' . $name . '_' . str_random((11 - strlen($name)))); + + } + + /** + * Adds a new server to the system. + * @param array $data An array of data descriptors for creating the server. These should align to the columns in the database. + */ + public function create(array $data) + { + + // Validate Fields + $validator = Validator::make($data, [ + 'owner' => 'required|email|exists:users,email', + 'node' => 'required|numeric|min:1|exists:nodes,id', + 'name' => 'required|regex:([\w -]{4,35})', + 'memory' => 'required|numeric|min:1', + 'disk' => 'required|numeric|min:1', + 'cpu' => 'required|numeric|min:0', + 'io' => 'required|numeric|min:10|max:1000', + 'ip' => 'required|ip', + 'port' => 'required|numeric|min:1|max:65535', + 'service' => 'required|numeric|min:1|exists:services,id', + 'option' => 'required|numeric|min:1|exists:service_options,id', + 'custom_image_name' => 'required_if:use_custom_image,on', + ]); + + // 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(json_encode($validator->errors()->all())); + } + + // 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(); + + // 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::find($data['node']); + $allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->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.'); + } + + // Validate those Service Option Variables + // We know the service and option exists because of the validation. + // We need to verify that the option exists for the service, and then check for + // any required variable fields. (fields are labeled env_) + $option = Models\ServiceOptions::where('id', $data['option'])->where('parent_service', $data['service'])->first(); + if (!$option) { + throw new DisplayException('The requested service option does not exist for the specified service.'); + } + + // Check those Variables + $variables = Models\ServiceVariables::where('option_id', $data['option'])->get(); + if ($variables) { + foreach($variables as $variable) { + + // Is the variable required? + if (!$data['env_' . $variable->env_variable]) { + if ($variable->required === 1) { + throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.'); + } + + $data['env_' . $variable->env_variable] = $variable->default_value; + continue; + } + + // Check aganist Regex Pattern + if (!is_null($variable->regex) && !preg_match($variable->regex, $data['env_' . $variable->env_variable])) { + throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').'); + } + + continue; + + } + } + + return (new UuidService)->generateShort(); + //return $this->generateSFTPUsername($data['name']); + + } + +} diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php new file mode 100644 index 000000000..bd3d48ce9 --- /dev/null +++ b/app/Repositories/UserRepository.php @@ -0,0 +1,44 @@ +uuid = $uuid->table('users')->generate(); + + $user->username = $username; + $user->email = $email; + $user->password = Hash::make($password); + + $user->save(); + + return $user->id; + + } + +} diff --git a/app/Services/UuidService.php b/app/Services/UuidService.php new file mode 100644 index 000000000..e9ecf7f76 --- /dev/null +++ b/app/Services/UuidService.php @@ -0,0 +1,101 @@ +table = $table; + return $this; + } + + /** + * Set the field in the given table that we want to check for a unique UUID. + * + * @param string $field + * @return void + */ + public function field($field) + { + $this->field = $field; + return $this; + } + + /** + * Generate a unique UUID validating against specified table and column. + * Defaults to `users.uuid` + * + * @param integer $type The type of UUID to generate. + * @return string + */ + public function generate($type = 4) + { + + $return = false; + do { + + $uuid = LaravelUUID::generate($type); + if (!DB::table($this->table)->where($this->field, $uuid)->exists()) { + $return = $uuid; + } + + } while (!$return); + + return $return; + + } + + /** + * Generates a ShortUUID code which is 8 characters long and is used for identifying servers in the system. + * + * @param string $table + * @param string $field + * @return string + */ + public function generateShort($table = 'servers', $field = 'uuidShort') + { + + $return = false; + do { + + $short = substr(Uuid::generate(4), 0, 8); + if (!DB::table($table)->where($field, $short)->exists()) { + $return = $short; + } + + } while (!$return); + + return $return; + + } + +}