Update user controller

This commit is contained in:
Dane Everitt 2017-01-12 15:40:24 -05:00
parent f292080483
commit e91362eee6
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
9 changed files with 200 additions and 77 deletions

View File

@ -7,10 +7,14 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
### Added ### Added
* Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon. * Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon.
* Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update. * Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update.
* Users can now have a username as well as client name assigned to thier account.
### Fixed ### Fixed
* Bug causing error logs to be spammed if someone timed out on an ajax based page.
### Changed ### Changed
* Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields.
* User model now defines mass assignment fields using `$fillable` rather than `$guarded`.
### Deprecated ### Deprecated

View File

@ -122,6 +122,9 @@ class UserController extends BaseController
{ {
try { try {
$user = new UserRepository; $user = new UserRepository;
$create = $user->create($request->only([
'email', 'username', 'name_first', 'name_last', 'password', 'root_admin', 'custom_id',
]));
$create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id')); $create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id'));
return ['id' => $create]; return ['id' => $create];
@ -156,7 +159,9 @@ class UserController extends BaseController
{ {
try { try {
$user = new UserRepository; $user = new UserRepository;
$user->update($id, $request->all()); $user->update($id, $request->only([
'username', 'email', 'name_first', 'name_last', 'password', 'root_admin', 'language',
]));
return Models\User::findOrFail($id); return Models\User::findOrFail($id);
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {

View File

@ -116,7 +116,13 @@ class UserController extends Controller
{ {
try { try {
$user = new UserRepository; $user = new UserRepository;
$userid = $user->create($request->input('email'), $request->input('password')); $userid = $user->create($request->only([
'email',
'password',
'name_first',
'name_last',
'username'
]));
Alert::success('Account has been successfully created.')->flash(); Alert::success('Account has been successfully created.')->flash();
return redirect()->route('admin.users.view', $userid); return redirect()->route('admin.users.view', $userid);
@ -132,19 +138,16 @@ class UserController extends Controller
public function updateUser(Request $request, $user) public function updateUser(Request $request, $user)
{ {
$data = [
'email' => $request->input('email'),
'root_admin' => $request->input('root_admin'),
'password_confirmation' => $request->input('password_confirmation'),
];
if ($request->input('password')) {
$data['password'] = $request->input('password');
}
try { try {
$repo = new UserRepository; $repo = new UserRepository;
$repo->update($user, $data); $repo->update($user, $request->only([
'email',
'password',
'name_first',
'name_last',
'username',
'root_admin',
]));
Alert::success('User account was successfully updated.')->flash(); Alert::success('User account was successfully updated.')->flash();
} catch (DisplayValidationException $ex) { } catch (DisplayValidationException $ex) {
return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage())); return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage()));

View File

@ -37,13 +37,24 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
class User extends Model implements class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{ {
use Authenticatable, Authorizable, CanResetPassword, Notifiable; use Authenticatable, Authorizable, CanResetPassword, Notifiable;
/**
* The rules for user passwords.
*
* @var string
*/
const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})';
/**
* The regex rules for usernames.
*
* @var string
*/
const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/';
/** /**
* The table associated with the model. * The table associated with the model.
* *
@ -52,11 +63,11 @@ class User extends Model implements
protected $table = 'users'; protected $table = 'users';
/** /**
* The attributes that are not mass assignable. * A list of mass-assignable variables.
* *
* @var array * @var [type]
*/ */
protected $guarded = ['id', 'remeber_token', 'created_at', 'updated_at']; protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar'];
/** /**
* Cast values to correct type. * Cast values to correct type.
@ -66,6 +77,7 @@ class User extends Model implements
protected $casts = [ protected $casts = [
'root_admin' => 'integer', 'root_admin' => 'integer',
'use_totp' => 'integer', 'use_totp' => 'integer',
'gravatar' => 'integer',
]; ];
/** /**
@ -76,12 +88,10 @@ class User extends Model implements
protected $hidden = ['password', 'remember_token', 'totp_secret']; protected $hidden = ['password', 'remember_token', 'totp_secret'];
/** /**
* The rules for user passwords. * Determines if a user has permissions.
* *
* @var string * @return bool
*/ */
const PASSWORD_RULES = 'min:8|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})';
public function permissions() public function permissions()
{ {
return $this->hasMany(Permission::class); return $this->hasMany(Permission::class);

View File

@ -29,6 +29,7 @@ use DB;
use Auth; use Auth;
use Hash; use Hash;
use Carbon; use Carbon;
use Settings;
use Validator; use Validator;
use Pterodactyl\Models; use Pterodactyl\Models;
use Pterodactyl\Services\UuidService; use Pterodactyl\Services\UuidService;
@ -52,18 +53,16 @@ class UserRepository
* @param int $token A custom user ID. * @param int $token A custom user ID.
* @return bool|int * @return bool|int
*/ */
public function create($email, $password = null, $admin = false, $token = null) public function create(array $data)
{ {
$validator = Validator::make([ $validator = Validator::make($data, [
'email' => $email,
'password' => $password,
'root_admin' => $admin,
'custom_id' => $token,
], [
'email' => 'required|email|unique:users,email', 'email' => 'required|email|unique:users,email',
'password' => 'nullable|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', 'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES,
'name_first' => 'required|string|between:1,255',
'name_last' => 'required|string|between:1,255',
'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES,
'root_admin' => 'required|boolean', 'root_admin' => 'required|boolean',
'custom_id' => 'nullable|unique:users,id', 'custom_id' => 'sometimes|nullable|unique:users,id',
]); ]);
// Run validator, throw catchable and displayable exception if it fails. // Run validator, throw catchable and displayable exception if it fails.
@ -79,26 +78,36 @@ class UserRepository
$uuid = new UuidService; $uuid = new UuidService;
// Support for API Services // Support for API Services
if (! is_null($token)) { if (isset($data['custom_id']) && ! is_null($data['custom_id'])) {
$user->id = $token; $user->id = $token;
} }
// UUIDs are not mass-fillable.
$user->uuid = $uuid->generate('users', 'uuid'); $user->uuid = $uuid->generate('users', 'uuid');
$user->email = $email;
$user->password = Hash::make((is_null($password)) ? str_random(30) : $password); $user->fill([
$user->language = 'en'; 'email' => $data['email'],
$user->root_admin = ($admin) ? 1 : 0; 'username' => $data['username'],
'name_first' => $data['name_first'],
'name_last' => $data['name_last'],
'password' => Hash::make((empty($data['password'])) ? str_random(30) : $password),
'root_admin' => $data['root_admin'],
'language' => Settings::get('default_language', 'en'),
]);
$user->save(); $user->save();
// Setup a Password Reset to use when they set a password. // Setup a Password Reset to use when they set a password.
$token = str_random(32); // Only used if no password is provided.
DB::table('password_resets')->insert([ if (empty($data['password'])) {
'email' => $user->email, $token = str_random(32);
'token' => $token, DB::table('password_resets')->insert([
'created_at' => Carbon::now()->toDateTimeString(), 'email' => $user->email,
]); 'token' => $token,
'created_at' => Carbon::now()->toDateTimeString(),
]);
$user->notify((new AccountCreated($token))); $user->notify((new AccountCreated($token)));
}
DB::commit(); DB::commit();
@ -122,7 +131,10 @@ class UserRepository
$validator = Validator::make($data, [ $validator = Validator::make($data, [
'email' => 'sometimes|required|email|unique:users,email,' . $id, 'email' => 'sometimes|required|email|unique:users,email,' . $id,
'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', 'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES,
'name_first' => 'sometimes|required|string|between:1,255',
'name_last' => 'sometimes|required|string|between:1,255',
'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES,
'root_admin' => 'sometimes|required|boolean', 'root_admin' => 'sometimes|required|boolean',
'language' => 'sometimes|required|string|min:1|max:5', 'language' => 'sometimes|required|string|min:1|max:5',
'use_totp' => 'sometimes|required|boolean', 'use_totp' => 'sometimes|required|boolean',
@ -135,12 +147,15 @@ class UserRepository
throw new DisplayValidationException($validator->errors()); throw new DisplayValidationException($validator->errors());
} }
if (array_key_exists('password', $data)) { // The password and root_admin fields are not mass assignable.
if (! empty($data['password'])) {
$data['password'] = Hash::make($data['password']); $data['password'] = Hash::make($data['password']);
} else {
unset($data['password']);
} }
if (isset($data['password_confirmation'])) { if (! empty($data['root_admin'])) {
unset($data['password_confirmation']); $user->root_admin = $data['root_admin'];
} }
$user->fill($data); $user->fill($data);

View File

@ -0,0 +1,50 @@
<?php
use Pterodactyl\Models\User;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddMoreUserData extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('name_first')->after('email')->nullable();
$table->string('name_last')->after('name_first')->nullable();
$table->string('username')->after('uuid');
$table->boolean('gravatar')->after('totp_secret')->default(true);
});
DB::transaction(function () {
foreach(User::all() as &$user) {
$user->username = $user->email;
$user->save();
}
});
Schema::table('users', function (Blueprint $table) {
$table->string('username')->unique()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('name_first');
$table->dropColumn('name_last');
$table->dropColumn('username');
$table->dropColumn('gravatar');
});
}
}

View File

@ -42,17 +42,21 @@
<table class="table table-striped table-bordered table-hover"> <table class="table table-striped table-bordered table-hover">
<thead> <thead>
<tr> <tr>
<th>Email</th> <th>ID</td>
<th>Account Created</th> <th>Email</td>
<th>Account Updated</th> <th>Client Name</th>
<th>Username</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach ($users as $user) @foreach ($users as $user)
<tr> <tr class="align-middle">
<td><a href="/admin/users/view/{{ $user->id }}"><code>{{ $user->email }}</code></a> @if($user->root_admin === 1)<span class="badge">Administrator</span>@endif</td> <td><code>#{{ $user->id }}</code></td>
<td>{{ $user->created_at }}</td> <td><a href="{{ route('admin.users.view', $user->id) }}">{{ $user->email }}</a></td>
<td>{{ $user->updated_at }}</td> <td>{{ $user->name_last }}, {{ $user->name_first }}</td>
<td><code>{{ $user->username }}</code></td>
<td class="text-center"><img src="https://www.gravatar.com/avatar/{{ md5(strtolower($user->email)) }}?s=20" class="img-circle" /></td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>

View File

@ -34,15 +34,38 @@
<h3>Create New Account</h3><hr /> <h3>Create New Account</h3><hr />
<form action="new" method="post"> <form action="new" method="post">
<fieldset> <fieldset>
<div class="form-group"> <div class="row">
<label for="email" class="control-label">Email</label> <div class="form-group col-md-6">
<div> <label for="email" class="control-label">Email</label>
<input type="text" autocomplete="off" name="email" class="form-control" /> <div>
<input type="text" autocomplete="off" name="email" value="{{ old('email') }}" class="form-control" />
</div>
</div>
<div class="form-group col-md-6">
<label for="username" class="control-label">Username</label>
<div>
<input type="text" autocomplete="off" name="username" value="{{ old('username') }}" class="form-control" />
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<label for="name_first" class="control-label">Client First Name</label>
<div>
<input type="text" autocomplete="off" name="name_first" value="{{ old('name_first') }}" class="form-control" />
</div>
</div>
<div class="form-group col-md-6">
<label for="name_last" class="control-label">Client Last Name</label>
<div>
<input type="text" autocomplete="off" name="name_last" value="{{ old('name_last') }}" class="form-control" />
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="well well-sm"> <hr />
<div class="alert alert-info">
<p>Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.</p> <p>Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.</p>
</div> </div>
</div> </div>

View File

@ -31,7 +31,9 @@
<li><a href="/admin/users">Accounts</a></li> <li><a href="/admin/users">Accounts</a></li>
<li class="active">{{ $user->email }}</li> <li class="active">{{ $user->email }}</li>
</ul> </ul>
<h3>Viewing User: {{ $user->email }}</h3><hr /> <h3 style="margin-bottom: 5px;">Viewing User: {{ $user->email }}</h3>
<p class="text-muted" style="margin: 0 0 -10.5px !important;"><small>Registered {{ (new Carbon($user->created_at))->toRfc1123String() }}</small></p>
<hr />
<div class="row"> <div class="row">
<form action="{{ route('admin.users.view', $user->id) }}" method="post"> <form action="{{ route('admin.users.view', $user->id) }}" method="post">
<div class="col-md-6"> <div class="col-md-6">
@ -43,19 +45,21 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="registered" class="control-label">{{ trans('strings.registered') }}</label> <label for="registered" class="control-label">Username</label>
<div> <div>
<input type="text" value="{{ $user->created_at }}" readonly="readonly" class="form-control"> <input type="text" name="username" value="{{ $user->username }}" class="form-control">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="root_admin" class="control-label">{{ trans('strings.root_administrator') }}</label> <label for="registered" class="control-label">Client First Name</label>
<div> <div>
<select name="root_admin" class="form-control"> <input type="text" name="name_first" value="{{ $user->name_first }}" class="form-control">
<option value="0">{{ trans('strings.no') }}</option> </div>
<option value="1" @if($user->root_admin)selected="selected"@endif>{{ trans('strings.yes') }}</option> </div>
</select> <div class="form-group">
<p class="text-muted"><small>Setting this to 'Yes' gives a user full administrative access.</small></p> <label for="registered" class="control-label">Client Last Name</label>
<div>
<input type="text" name="name_last" value="{{ $user->name_last }}" class="form-control">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -66,7 +70,6 @@
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="well" style="padding-bottom: 0;"> <div class="well" style="padding-bottom: 0;">
<h4 class="nopad">{{ trans('base.account.update_pass') }}</h5><hr />
<div class="alert alert-success" style="display:none;margin-bottom:10px;" id="gen_pass"></div> <div class="alert alert-success" style="display:none;margin-bottom:10px;" id="gen_pass"></div>
<div class="form-group"> <div class="form-group">
<label for="password" class="control-label">{{ trans('strings.password') }}</label> <label for="password" class="control-label">{{ trans('strings.password') }}</label>
@ -74,16 +77,22 @@
<input type="password" id="password" name="password" class="form-control"> <input type="password" id="password" name="password" class="form-control">
</div> </div>
</div> </div>
<div class="form-group">
<label for="password_confirmation" class="control-label">{{ trans('auth.confirmpassword') }}</label>
<div>
<input type="password" id="password_confirmation" name="password_confirmation" class="form-control">
</div>
</div>
<div class="form-group"> <div class="form-group">
<button class="btn btn-default btn-sm" id="gen_pass_bttn" type="button">Generate Password</button> <button class="btn btn-default btn-sm" id="gen_pass_bttn" type="button">Generate Password</button>
</div> </div>
</div> </div>
<div class="well" style="padding-bottom: 0;">
<div class="form-group">
<label for="root_admin" class="control-label">{{ trans('strings.root_administrator') }}</label>
<div>
<select name="root_admin" class="form-control">
<option value="0">{{ trans('strings.no') }}</option>
<option value="1" @if($user->root_admin)selected="selected"@endif>{{ trans('strings.yes') }}</option>
</select>
<p class="text-muted"><small>Setting this to 'Yes' gives a user full administrative access.</small></p>
</div>
</div>
</div>
</div> </div>
</form> </form>
</div> </div>