Compare commits
7 commits
develop
...
0.7-develo
Author | SHA1 | Date | |
---|---|---|---|
|
4028588621 | ||
|
1cd08e2f8d | ||
|
dcf5cb3cd3 | ||
|
6d69f6ef47 | ||
|
78514f9eb4 | ||
|
7deed07cd1 | ||
|
597196ad3e |
8 changed files with 68 additions and 28 deletions
|
@ -3,6 +3,15 @@ This file is a running track of new features and fixes to each version of the pa
|
||||||
|
|
||||||
This project follows [Semantic Versioning](http://semver.org) guidelines.
|
This project follows [Semantic Versioning](http://semver.org) guidelines.
|
||||||
|
|
||||||
|
## v0.7.19 (Derelict Dermodactylus)
|
||||||
|
### Fixed
|
||||||
|
* **[Security]** Fixes XSS in the admin area's server owner selection.
|
||||||
|
|
||||||
|
## v0.7.18 (Derelict Dermodactylus)
|
||||||
|
### Fixed
|
||||||
|
* **[Security]** Re-addressed missed endpoint that would not properly limit a user account to 5 API keys.
|
||||||
|
* **[Security]** Addresses a Client API vulnerability that would allow a user to list all servers on the system ([`GHSA-6888-7f3w-92jx`](https://github.com/pterodactyl/panel/security/advisories/GHSA-6888-7f3w-92jx))
|
||||||
|
|
||||||
## v0.7.17 (Derelict Dermodactylus)
|
## v0.7.17 (Derelict Dermodactylus)
|
||||||
### Fixed
|
### Fixed
|
||||||
* Limited accounts to 5 API keys at a time.
|
* Limited accounts to 5 API keys at a time.
|
||||||
|
|
|
@ -82,10 +82,13 @@ class AccountKeyController extends Controller
|
||||||
*/
|
*/
|
||||||
public function store(StoreAccountKeyRequest $request)
|
public function store(StoreAccountKeyRequest $request)
|
||||||
{
|
{
|
||||||
if ($this->repository->findCountWhere(['user_id' => $request->user()->id]) >= 5) {
|
$count = $this->repository->findCountWhere([
|
||||||
throw new DisplayException(
|
['user_id', '=', $request->user()->id],
|
||||||
'Cannot assign more than 5 API keys to an account.'
|
['key_type', '=', ApiKey::TYPE_ACCOUNT],
|
||||||
);
|
]);
|
||||||
|
|
||||||
|
if ($count >= 5) {
|
||||||
|
throw new DisplayException('Cannot assign more than 5 API keys to an account.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([
|
$this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([
|
||||||
|
|
|
@ -8,6 +8,7 @@ use Illuminate\Http\Response;
|
||||||
use Pterodactyl\Models\ApiKey;
|
use Pterodactyl\Models\ApiKey;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
use Prologue\Alerts\AlertsMessageBag;
|
||||||
|
use Pterodactyl\Exceptions\DisplayException;
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
use Pterodactyl\Services\Api\KeyCreationService;
|
use Pterodactyl\Services\Api\KeyCreationService;
|
||||||
use Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest;
|
use Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest;
|
||||||
|
@ -73,10 +74,20 @@ class ClientApiController extends Controller
|
||||||
* @param \Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest $request
|
* @param \Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest $request
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
*/
|
*/
|
||||||
public function store(CreateClientApiKeyRequest $request): RedirectResponse
|
public function store(CreateClientApiKeyRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$count = $this->repository->findCountWhere([
|
||||||
|
['user_id', '=', $request->user()->id],
|
||||||
|
['key_type', '=', ApiKey::TYPE_ACCOUNT],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($count >= 5) {
|
||||||
|
throw new DisplayException('Cannot assign more than 5 API keys to an account.');
|
||||||
|
}
|
||||||
|
|
||||||
$allowedIps = null;
|
$allowedIps = null;
|
||||||
if (! is_null($request->input('allowed_ips'))) {
|
if (! is_null($request->input('allowed_ips'))) {
|
||||||
$allowedIps = json_encode(explode(PHP_EOL, $request->input('allowed_ips')));
|
$allowedIps = json_encode(explode(PHP_EOL, $request->input('allowed_ips')));
|
||||||
|
|
|
@ -225,18 +225,23 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
|
||||||
$instance->where('owner_id', $user->id);
|
$instance->where('owner_id', $user->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If set to all, display all servers they can access, including
|
// Only allow these two filters if the user is an administrator.
|
||||||
// those they access as an admin. If set to subuser, only return
|
elseif ($user->root_admin && in_array($level, [ User::FILTER_LEVEL_ALL, User::FILTER_LEVEL_ADMIN ])) {
|
||||||
// the servers they can access because they are owner, or marked
|
// We specifically only match admin in here. If they request all servers and are a root admin
|
||||||
// as a subuser of the server.
|
// we just won't append any filters to the builder and thus they'll be able to see everything
|
||||||
elseif (($level === User::FILTER_LEVEL_ALL && ! $user->root_admin) || $level === User::FILTER_LEVEL_SUBUSER) {
|
// since this will skip over that final else block.
|
||||||
$instance->whereIn('id', $this->getUserAccessServers($user->id));
|
if ($level === User::FILTER_LEVEL_ADMIN) {
|
||||||
|
$instance->whereNotIn('id', $this->getUserAccessServers($user->id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If set to admin, only display the servers a user can access
|
// If we did not match on the user being an administrator and requesting all/admin only or the user
|
||||||
// as an administrator (leaves out owned and subuser of).
|
// is not an admin and requested those locked endpoints, just return all of the servers the user actually
|
||||||
elseif ($level === User::FILTER_LEVEL_ADMIN && $user->root_admin) {
|
// has access to.
|
||||||
$instance->whereNotIn('id', $this->getUserAccessServers($user->id));
|
//
|
||||||
|
// @see https://github.com/pterodactyl/panel/security/advisories/GHSA-6888-7f3w-92jx
|
||||||
|
else {
|
||||||
|
$instance->whereIn('id', $this->getUserAccessServers($user->id));
|
||||||
}
|
}
|
||||||
|
|
||||||
$instance->search($this->getSearchTerm());
|
$instance->search($this->getSearchTerm());
|
||||||
|
|
|
@ -37,6 +37,12 @@ $(document).ready(function() {
|
||||||
placeholder: 'Select Additional Allocations',
|
placeholder: 'Select Additional Allocations',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.appendChild(document.createTextNode(str));
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
$('#pUserId').select2({
|
$('#pUserId').select2({
|
||||||
ajax: {
|
ajax: {
|
||||||
url: Router.route('admin.users.json'),
|
url: Router.route('admin.users.json'),
|
||||||
|
@ -56,23 +62,23 @@ $(document).ready(function() {
|
||||||
escapeMarkup: function (markup) { return markup; },
|
escapeMarkup: function (markup) { return markup; },
|
||||||
minimumInputLength: 2,
|
minimumInputLength: 2,
|
||||||
templateResult: function (data) {
|
templateResult: function (data) {
|
||||||
if (data.loading) return data.text;
|
if (data.loading) return escapeHtml(data.text);
|
||||||
|
|
||||||
return '<div class="user-block"> \
|
return '<div class="user-block"> \
|
||||||
<img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" alt="User Image"> \
|
<img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + escapeHtml(data.md5) + '?s=120" alt="User Image"> \
|
||||||
<span class="username"> \
|
<span class="username"> \
|
||||||
<a href="#">' + data.name_first + ' ' + data.name_last +'</a> \
|
<a href="#">' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) +'</a> \
|
||||||
</span> \
|
</span> \
|
||||||
<span class="description"><strong>' + data.email + '</strong> - ' + data.username + '</span> \
|
<span class="description"><strong>' + escapeHtml(data.email) + '</strong> - ' + escapeHtml(data.username) + '</span> \
|
||||||
</div>';
|
</div>';
|
||||||
},
|
},
|
||||||
templateSelection: function (data) {
|
templateSelection: function (data) {
|
||||||
return '<div> \
|
return '<div> \
|
||||||
<span> \
|
<span> \
|
||||||
<img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \
|
<img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + escapeHtml(data.md5) + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \
|
||||||
</span> \
|
</span> \
|
||||||
<span style="padding-left:5px;"> \
|
<span style="padding-left:5px;"> \
|
||||||
' + data.name_first + ' ' + data.name_last + ' (<strong>' + data.email + '</strong>) \
|
' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) + ' (<strong>' + escapeHtml(data.email) + '</strong>) \
|
||||||
</span> \
|
</span> \
|
||||||
</div>';
|
</div>';
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,5 +259,5 @@
|
||||||
@section('footer-scripts')
|
@section('footer-scripts')
|
||||||
@parent
|
@parent
|
||||||
{!! Theme::js('vendor/lodash/lodash.js') !!}
|
{!! Theme::js('vendor/lodash/lodash.js') !!}
|
||||||
{!! Theme::js('js/admin/new-server.js') !!}
|
{!! Theme::js('js/admin/new-server.js?v=20201003.100600') !!}
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
@ -83,6 +83,12 @@
|
||||||
@section('footer-scripts')
|
@section('footer-scripts')
|
||||||
@parent
|
@parent
|
||||||
<script>
|
<script>
|
||||||
|
function escapeHtml(str) {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.appendChild(document.createTextNode(str));
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
$('#pUserId').select2({
|
$('#pUserId').select2({
|
||||||
ajax: {
|
ajax: {
|
||||||
url: Router.route('admin.users.json'),
|
url: Router.route('admin.users.json'),
|
||||||
|
@ -102,14 +108,14 @@
|
||||||
escapeMarkup: function (markup) { return markup; },
|
escapeMarkup: function (markup) { return markup; },
|
||||||
minimumInputLength: 2,
|
minimumInputLength: 2,
|
||||||
templateResult: function (data) {
|
templateResult: function (data) {
|
||||||
if (data.loading) return data.text;
|
if (data.loading) return escapeHtml(data.text);
|
||||||
|
|
||||||
return '<div class="user-block"> \
|
return '<div class="user-block"> \
|
||||||
<img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" alt="User Image"> \
|
<img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + escapeHtml(data.md5) + '?s=120" alt="User Image"> \
|
||||||
<span class="username"> \
|
<span class="username"> \
|
||||||
<a href="#">' + data.name_first + ' ' + data.name_last +'</a> \
|
<a href="#">' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) +'</a> \
|
||||||
</span> \
|
</span> \
|
||||||
<span class="description"><strong>' + data.email + '</strong> - ' + data.username + '</span> \
|
<span class="description"><strong>' + escapeHtml(data.email) + '</strong> - ' + escapeHtml(data.username) + '</span> \
|
||||||
</div>';
|
</div>';
|
||||||
},
|
},
|
||||||
templateSelection: function (data) {
|
templateSelection: function (data) {
|
||||||
|
@ -125,10 +131,10 @@
|
||||||
|
|
||||||
return '<div> \
|
return '<div> \
|
||||||
<span> \
|
<span> \
|
||||||
<img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \
|
<img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + escapeHtml(data.md5) + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \
|
||||||
</span> \
|
</span> \
|
||||||
<span style="padding-left:5px;"> \
|
<span style="padding-left:5px;"> \
|
||||||
' + data.name_first + ' ' + data.name_last + ' (<strong>' + data.email + '</strong>) \
|
' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) + ' (<strong>' + escapeHtml(data.email) + '</strong>) \
|
||||||
</span> \
|
</span> \
|
||||||
</div>';
|
</div>';
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
@if (! empty($server->description))
|
@if (! empty($server->description))
|
||||||
<tr class="server-description">
|
<tr class="server-description">
|
||||||
<td colspan="7"><p class="text-muted small no-margin">{{ str_limit($server->description, 400) }}</p></td>
|
<td colspan="8"><p class="text-muted small no-margin">{{ str_limit($server->description, 400) }}</p></td>
|
||||||
</tr>
|
</tr>
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
Loading…
Reference in a new issue