Merge branch 'v2' into dane/webauthn
This commit is contained in:
commit
8971e78ab5
269 changed files with 6005 additions and 3308 deletions
1
.env.ci
1
.env.ci
|
@ -5,6 +5,7 @@ APP_THEME=pterodactyl
|
|||
APP_TIMEZONE=America/Los_Angeles
|
||||
APP_URL=http://localhost/
|
||||
|
||||
DB_CONNECTION=testing
|
||||
TESTING_DB_HOST=127.0.0.1
|
||||
TESTING_DB_DATABASE=panel_test
|
||||
TESTING_DB_USERNAME=root
|
||||
|
|
|
@ -8,6 +8,7 @@ APP_DELETE_MINUTES=10
|
|||
APP_ENVIRONMENT_ONLY=true
|
||||
LOG_CHANNEL=daily
|
||||
APP_LOCALE=en
|
||||
APP_URL=http://panel.example.com
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
|
@ -30,7 +31,7 @@ MAILGUN_ENDPOINT=api.mailgun.net
|
|||
# mail servers such as Gmail to reject your mail.
|
||||
#
|
||||
# @see: https://github.com/pterodactyl/panel/pull/3110
|
||||
# SERVER_NAME=panel.yourdomain.com
|
||||
# SERVER_NAME=panel.example.com
|
||||
|
||||
QUEUE_HIGH=high
|
||||
QUEUE_STANDARD=standard
|
||||
|
|
|
@ -43,6 +43,10 @@ rules:
|
|||
array-bracket-spacing:
|
||||
- warn
|
||||
- always
|
||||
"@typescript-eslint/no-unused-vars":
|
||||
- warn
|
||||
- argsIgnorePattern: '^_'
|
||||
varsIgnorePattern: '^_'
|
||||
# Remove errors for not having newlines between operands of ternary expressions https://eslint.org/docs/rules/multiline-ternary
|
||||
multiline-ternary: 0
|
||||
"react-hooks/rules-of-hooks":
|
||||
|
|
36
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
36
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
|
@ -7,14 +7,6 @@ body:
|
|||
value: |
|
||||
Bug reports should only be used for reporting issues with how the software works. For assistance installing this software, as well as debugging issues with dependencies, please use our [Discord server](https://discord.gg/pterodactyl).
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please [search here](https://github.com/pterodactyl/panel/issues) to see if an issue already exists for your problem.
|
||||
options:
|
||||
- label: I have searched the existing issues before opening this issue.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
|
@ -32,7 +24,7 @@ body:
|
|||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Please be as detailed as possible when providing steps to reproduce, failure to provide steps will likely result in this issue being closed.
|
||||
description: Please be as detailed as possible when providing steps to reproduce, failure to provide steps will result in this issue being closed.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
@ -53,6 +45,20 @@ body:
|
|||
placeholder: 1.4.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: egg-details
|
||||
attributes:
|
||||
label: Games and/or Eggs Affected
|
||||
description: Please include the specific game(s) or egg(s) you are running into this bug with.
|
||||
placeholder: Minecraft (Paper), Minecraft (Forge)
|
||||
|
||||
- type: input
|
||||
id: docker-image
|
||||
attributes:
|
||||
label: Docker Image
|
||||
description: The specific Docker image you are using for the game(s) above.
|
||||
placeholder: ghcr.io/pterodactyl/yolks:java_17
|
||||
|
||||
- type: textarea
|
||||
id: panel-logs
|
||||
|
@ -67,3 +73,15 @@ body:
|
|||
render: bash
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please [search here](https://github.com/pterodactyl/panel/issues) to see if an issue already exists for your problem.
|
||||
options:
|
||||
- label: I have searched the existing issues before opening this issue.
|
||||
required: true
|
||||
- label: I have provided all relevant details, including the specific game and Docker images I am using if this issue is related to running a server.
|
||||
required: true
|
||||
- label: I have checked in the Discord server and believe this is a bug with the software, and not a configuration issue with my specific system.
|
||||
required: true
|
||||
|
|
4
.github/docker/README.md
vendored
4
.github/docker/README.md
vendored
|
@ -12,7 +12,7 @@ You can provide additional settings using a custom `.env` file or by setting the
|
|||
|
||||
## Setup
|
||||
|
||||
Start the docker container and the required dependencies (either provide existing ones or start containers as well, see the [docker-compose.yml](docker-compose.yml) file as an example).
|
||||
Start the docker container and the required dependencies (either provide existing ones or start containers as well, see the [docker-compose.yml](https://github.com/pterodactyl/panel/blob/develop/docker-compose.example.yml) file as an example.
|
||||
|
||||
After the startup is complete you'll need to create a user.
|
||||
If you are running the docker container without docker-compose, use:
|
||||
|
@ -33,7 +33,7 @@ Note: If your `APP_URL` starts with `https://` you need to provide an `LETSENCRY
|
|||
| ------------------- | ------------------------------------------------------------------------------ | -------- |
|
||||
| `APP_URL` | The URL the panel will be reachable with (including protocol) | yes |
|
||||
| `APP_TIMEZONE` | The timezone to use for the panel | yes |
|
||||
| `LETSENCRYPT_EMAIL` | The email used for letsencrypt certificate generation | yes |
|
||||
| `LE_EMAIL` | The email used for letsencrypt certificate generation | yes |
|
||||
| `DB_HOST` | The host of the mysql instance | yes |
|
||||
| `DB_PORT` | The port of the mysql instance | yes |
|
||||
| `DB_DATABASE` | The name of the mysql database | yes |
|
||||
|
|
17
.github/docker/entrypoint.sh
vendored
17
.github/docker/entrypoint.sh
vendored
|
@ -30,7 +30,7 @@ else
|
|||
fi
|
||||
|
||||
echo "Checking if https is required."
|
||||
if [ -f /etc/nginx/conf.d/default.conf ]; then
|
||||
if [ -f /etc/nginx/http.d/panel.conf ]; then
|
||||
echo "Using nginx config already in place."
|
||||
if [ $LE_EMAIL ]; then
|
||||
echo "Checking for cert update"
|
||||
|
@ -42,20 +42,27 @@ else
|
|||
echo "Checking if letsencrypt email is set."
|
||||
if [ -z $LE_EMAIL ]; then
|
||||
echo "No letsencrypt email is set using http config."
|
||||
cp .github/docker/default.conf /etc/nginx/conf.d/default.conf
|
||||
cp .github/docker/default.conf /etc/nginx/http.d/panel.conf
|
||||
else
|
||||
echo "writing ssl config"
|
||||
cp .github/docker/default_ssl.conf /etc/nginx/conf.d/default.conf
|
||||
cp .github/docker/default_ssl.conf /etc/nginx/http.d/panel.conf
|
||||
echo "updating ssl config for domain"
|
||||
sed -i "s|<domain>|$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/conf.d/default.conf
|
||||
sed -i "s|<domain>|$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/http.d/panel.conf
|
||||
echo "generating certs"
|
||||
certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n
|
||||
fi
|
||||
echo "Removing the default nginx config"
|
||||
rm -rf /etc/nginx/http.d/default.conf
|
||||
fi
|
||||
|
||||
if [[ -z $DB_PORT ]]; then
|
||||
echo -e "DB_PORT not specified, defaulting to 3306"
|
||||
DB_PORT=3306
|
||||
fi
|
||||
|
||||
## check for DB up before starting the panel
|
||||
echo "Checking database status."
|
||||
until nc -z -v -w30 $DB_HOST 3306
|
||||
until nc -z -v -w30 $DB_HOST $DB_PORT
|
||||
do
|
||||
echo "Waiting for database connection..."
|
||||
# wait for 1 seconds before check again
|
||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -36,6 +36,7 @@ jobs:
|
|||
if: "!contains(github.ref, 'develop')"
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
- name: Release Development Build
|
||||
|
@ -43,5 +44,6 @@ jobs:
|
|||
if: "contains(github.ref, 'develop')"
|
||||
with:
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
|
|
16
.github/workflows/tests.yml
vendored
16
.github/workflows/tests.yml
vendored
|
@ -37,9 +37,7 @@ jobs:
|
|||
path: |
|
||||
~/.php_cs.cache
|
||||
${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cache-${{ matrix.php }}-
|
||||
key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('composer.lock') }}
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
|
@ -52,16 +50,22 @@ jobs:
|
|||
- name: composer install
|
||||
run: composer install --prefer-dist --no-interaction --no-progress
|
||||
- name: Run cs-fixer
|
||||
run: vendor/bin/php-cs-fixer fix --dry-run --diff --diff-format=udiff --config .php-cs-fixer.dist.php
|
||||
run: vendor/bin/php-cs-fixer fix --dry-run --diff --format=txt --config .php-cs-fixer.dist.php
|
||||
continue-on-error: true
|
||||
- name: Static Analysis
|
||||
if: ${{ matrix.php }} == '8.0'
|
||||
run: |
|
||||
php artisan ide-helper:models -N
|
||||
./vendor/bin/phpstan analyse --memory-limit=2G
|
||||
env:
|
||||
TESTING_DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
- name: Execute Unit Tests
|
||||
run: php artisan test tests/Unit
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
TESTING_DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
TESTING_DB_USERNAME: root
|
||||
- name: Execute Integration Tests
|
||||
run: php artisan test tests/Integration
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
TESTING_DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
TESTING_DB_USERNAME: root
|
||||
|
|
|
@ -10,6 +10,9 @@ $finder = (new Finder())
|
|||
'node_modules',
|
||||
'storage',
|
||||
'bootstrap/cache',
|
||||
'.phpstorm.meta.php',
|
||||
'_ide_helper.php',
|
||||
'_ide_helper_models.php',
|
||||
])
|
||||
->notName(['_ide_helper*']);
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ Release versions of Pterodactyl will include pre-compiled, minified, and hashed
|
|||
However, if you are interested in running custom themes or making modifications to the React files you'll need a build
|
||||
system in place to generate these compiled assets. To get your environment setup you'll need at minimum:
|
||||
|
||||
* Node.js 12
|
||||
* [Yarn](https://classic.yarnpkg.com/lang/en/) v1
|
||||
* [Go](https://golang.org/) 1.15.
|
||||
* [Node.js](https://nodejs.org/en/) v14.x.x
|
||||
* [Yarn](https://classic.yarnpkg.com/lang/en/) v1.x.x
|
||||
* [Go](https://golang.org/) 1.17.x
|
||||
|
||||
### Install Dependencies
|
||||
```bash
|
||||
|
|
56
CHANGELOG.md
56
CHANGELOG.md
|
@ -3,6 +3,62 @@ 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.
|
||||
|
||||
## v1.7.0
|
||||
### Fixed
|
||||
* Fixes typo in message shown to user when deleting a database.
|
||||
* Fixes formatting of IPv6 addresses when displaying allocations to users.
|
||||
* Fixes an exception thrown while trying to return error messages from API endpoints that inproperly masked the true underlying error.
|
||||
* Fixes SSL certificate path generation for Let's Encrypt by ensuring they are always transformed to lowercase.
|
||||
* Removes duplicate entries when creating a nested folder in the file manager.
|
||||
* Fixes missing validation of Egg Author email addresses during the setup process that could cause unexpected failures later on.
|
||||
* Fixes font rendering issues of the console on Firefox due to an outdated version of xterm.js being used.
|
||||
* Fixes display overlap issues of the two-factor configuration form in a user's settings.
|
||||
* **[security]** When authenticating using an API key a user session is now only persisted for the duration of the request before being destroyed.
|
||||
|
||||
### Changed
|
||||
* CPU graph changed to show the maximum amount of CPU available to a server to better match how the memory graph is displayed.
|
||||
|
||||
### Added
|
||||
* Adds support for `DB_PORT` environment variable in the Docker enterpoint for the Panel image.
|
||||
* Adds suport for ARM environments in the Docker image.
|
||||
* Adds a new warning modal for Steam servers shown when an invalid Game Server Login Token (GSL Token) is detected.
|
||||
* Adds a new warning modal for Steam servers shown when the installation process runs out of available disk space.
|
||||
* Adds a new warning modal for Minecraft servers shown when a server exceeds the maximum number of child processes.
|
||||
* Adds support for displaying certain server variable fields as a checkbox when they're detected as using `boolean` or `in:0,1` validation rules.
|
||||
* Adds support for Pug and Jade in the file editor.
|
||||
* Adds an entry to the `robots.txt` file to correctly disallow all bot indexing.
|
||||
|
||||
|
||||
## v1.6.6
|
||||
### Fixed
|
||||
* **[security]** Fixes a CSRF vulnerability for both the administrative test email endpoint and node auto-deployment token generation endpoint. [GHSA-wwgq-9jhf-qgw6](https://github.com/pterodactyl/panel/security/advisories/GHSA-wwgq-9jhf-qgw6)
|
||||
|
||||
### Changed
|
||||
* Updates Minecraft eggs to include latest Java 17 yolk by default.
|
||||
|
||||
## v1.6.5
|
||||
### Fixed
|
||||
* Fixes broken application API endpoints due to changes introduced with session management in 1.6.4.
|
||||
|
||||
## v1.6.4
|
||||
_This release should not be used, please use `1.6.5`. It has been pulled from our releases._
|
||||
|
||||
### Fixed
|
||||
* Fixes a session management bug that would cause a user who signs out of one browser to be unintentionally logged out of other browser sessions when using the client API.
|
||||
|
||||
## v1.6.3
|
||||
### Fixed
|
||||
* **[Security]** Changes logout endpoint to be a POST request with CSRF-token validation to prevent a malicious actor from triggering a user logout.
|
||||
* Fixes Wings receiving the wrong server suspension state when syncing servers.
|
||||
|
||||
### Added
|
||||
* Adds additional throttling to login and password reset endpoints.
|
||||
* Adds server uptime display when viewing a server console.
|
||||
|
||||
## v1.6.2
|
||||
### Fixed
|
||||
* **[Security]** Fixes an authentication bypass vulerability that could allow a malicious actor to login as another user in the Panel without knowing that user's email or password.
|
||||
|
||||
## v1.6.1
|
||||
### Fixed
|
||||
* Fixes server build modifications not being properly persisted to the database when edited.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Build the assets that are needed for the frontend. This build stage is then discarded
|
||||
# since we won't need NodeJS anymore in the future. This Docker image ships a final production
|
||||
# level distribution of Pterodactyl.
|
||||
FROM mhart/alpine-node:14
|
||||
FROM --platform=$TARGETOS/$TARGETARCH mhart/alpine-node:14
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN yarn install --frozen-lockfile \
|
||||
|
@ -10,11 +10,11 @@ RUN yarn install --frozen-lockfile \
|
|||
|
||||
# Stage 1:
|
||||
# Build the actual container with all of the needed PHP dependencies that will run the application.
|
||||
FROM php:7.4-fpm-alpine
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:7.4-fpm-alpine
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
COPY --from=0 /app/public/assets ./public/assets
|
||||
RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar unzip nginx libpng-dev libxml2-dev libzip-dev certbot \
|
||||
RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar unzip nginx libpng-dev libxml2-dev libzip-dev certbot certbot-nginx \
|
||||
&& docker-php-ext-configure zip \
|
||||
&& docker-php-ext-install bcmath gd pdo_mysql zip \
|
||||
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
||||
|
@ -27,6 +27,7 @@ RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar un
|
|||
|
||||
RUN rm /usr/local/etc/php-fpm.conf \
|
||||
&& echo "* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1" >> /var/spool/cron/crontabs/root \
|
||||
&& echo "0 23 * * * certbot renew --nginx --quiet" >> /var/spool/cron/crontabs/root \
|
||||
&& sed -i s/ssl_session_cache/#ssl_session_cache/g /etc/nginx/nginx.conf \
|
||||
&& mkdir -p /var/run/php /var/run/nginx
|
||||
|
||||
|
@ -35,5 +36,5 @@ COPY .github/docker/www.conf /usr/local/etc/php-fpm.conf
|
|||
COPY .github/docker/supervisord.conf /etc/supervisord.conf
|
||||
|
||||
EXPOSE 80 443
|
||||
ENTRYPOINT ["/bin/ash", ".github/docker/entrypoint.sh"]
|
||||
ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ]
|
||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# The MIT License (MIT)
|
||||
|
||||
```
|
||||
Copyright (c) 2015 - 2021 Dane Everitt <dane@daneeveritt.com> and Contributors
|
||||
Pterodactyl®
|
||||
Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
46
README.md
46
README.md
|
@ -6,7 +6,7 @@
|
|||
![GitHub contributors](https://img.shields.io/github/contributors/pterodactyl/panel?style=for-the-badge)
|
||||
|
||||
# Pterodactyl Panel
|
||||
Pterodactyl is an open-source game server management panel built with PHP 7, React, and Go. Designed with security
|
||||
Pterodactyl® is a free, open-source game server management panel built with PHP, React, and Go. Designed with security
|
||||
in mind, Pterodactyl runs all game servers in isolated Docker containers while exposing a beautiful and intuitive
|
||||
UI to end users.
|
||||
|
||||
|
@ -14,6 +14,12 @@ Stop settling for less. Make game servers a first class citizen on your platform
|
|||
|
||||
![Image](https://cdn.pterodactyl.io/site-assets/pterodactyl_v1_demo.gif)
|
||||
|
||||
## Documentation
|
||||
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
|
||||
* [Wings Documentation](https://pterodactyl.io/wings/1.0/installing.html)
|
||||
* [Community Guides](https://pterodactyl.io/community/about.html)
|
||||
* Or, get additional help [via Discord](https://discord.gg/pterodactyl)
|
||||
|
||||
## Sponsors
|
||||
I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's developement.
|
||||
[Interested in becoming a sponsor?](https://github.com/sponsors/DaneEveritt)
|
||||
|
@ -32,21 +38,16 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
|||
| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims for inexpensive services on quality servers. Premium i9-9900K processors will run your game like a dream. |
|
||||
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
||||
| [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. |
|
||||
| [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
|
||||
| [**Capitol Hosting Solutions**](https://chs.gg/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
|
||||
| [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! |
|
||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.|
|
||||
| [**RocketNode**](https://rocketnode.net) | RocketNode is a VPS and Game Server provider that offers the best performing VPS and Game hosting Solutions at affordable prices! |
|
||||
|
||||
## Documentation
|
||||
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
|
||||
* [Wings Documentation](https://pterodactyl.io/wings/1.0/installing.html)
|
||||
* [Community Guides](https://pterodactyl.io/community/about.html)
|
||||
* Or, get additional help [via Discord](https://discord.gg/pterodactyl)
|
||||
| [**HostEZ**](https://hostez.io) | Providing North America Valheim, Minecraft and other popular games with low latency, high uptime and maximum availability. EZ! |
|
||||
|
||||
### Supported Games
|
||||
We support a huge variety of games by utilizing Docker containers to isolate each instance, giving you the power to
|
||||
host your games across the world without having to bloat each physical machine with additional dependencies.
|
||||
Pterodactyl supports a wide variety of games by utilizing Docker containers to isolate each instance. This gives
|
||||
you the power to run game servers without bloating machines with a host of additional dependencies.
|
||||
|
||||
Some of our core supported games include:
|
||||
|
||||
|
@ -73,27 +74,6 @@ and there are plenty more games available provided by the community. Some of the
|
|||
* [and many more...](https://github.com/parkervcp/eggs)
|
||||
|
||||
## License
|
||||
```
|
||||
Copyright (c) 2015 - 2021 Dane Everitt <dane@daneeveritt.com> and Contributors
|
||||
Pterodactyl® Copyright © 2015 - 2022 Dane Everitt and contributors.
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
Some Javascript and CSS used within the panel are licensed under a `MIT` or `Apache 2.0` license. Please check their
|
||||
respective header files for more information.
|
||||
Code released under the [MIT License](./LICENSE.md).
|
||||
|
|
11
SECURITY.md
11
SECURITY.md
|
@ -5,18 +5,13 @@ The following versions of Pterodactyl are receiving active support and maintenan
|
|||
|
||||
| Panel | Daemon | Supported |
|
||||
| ----- | ------------ | ------------------ |
|
||||
| 1.4.x | wings@1.4.x | :white_check_mark: |
|
||||
| 1.3.x | wings@1.3.x | :x: |
|
||||
| 1.2.x | wings@1.2.x | :x: |
|
||||
| 1.1.x | wings@1.1.x | :x: |
|
||||
| 1.0.x | wings@1.0.x | :x: |
|
||||
| 1.7.x | wings@1.5.x | :white_check_mark: |
|
||||
| 0.7.x | daemon@0.6.x | :x: |
|
||||
| 0.6.x | daemon@0.5.x | :x: |
|
||||
| 0.5.x | daemon@0.4.x | :x: |
|
||||
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please reach out directly to any project team member on Discord when reporting a security vulnerability, or you can send an email to `dane [ät] pterodactyl.io`.
|
||||
Please reach out directly to any project team member on Discord when reporting a security vulnerability, or you can send an email to `dane@pterodactyl.io`.
|
||||
|
||||
We make every effort to respond as soon as possible, although it may take a day or two for us to sync internally and determine the severity of the report and its impact. Please, _do not_ use a public facing channel or GitHub issues to report sensitive security issues.
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace Pterodactyl\Console\Commands\Environment;
|
|||
use DateTimeZone;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Validation\Factory as ValidatorFactory;
|
||||
use Pterodactyl\Traits\Commands\EnvironmentWriterTrait;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
|
||||
|
@ -78,12 +79,13 @@ class AppSettingsCommand extends Command
|
|||
/**
|
||||
* AppSettingsCommand constructor.
|
||||
*/
|
||||
public function __construct(ConfigRepository $config, Kernel $command)
|
||||
public function __construct(ConfigRepository $config, Kernel $command, ValidatorFactory $validator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->command = $command;
|
||||
$this->config = $config;
|
||||
$this->command = $command;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,6 +105,18 @@ class AppSettingsCommand extends Command
|
|||
$this->config->get('pterodactyl.service.author', 'unknown@unknown.com')
|
||||
);
|
||||
|
||||
$validator = $this->validator->make(
|
||||
['email' => $this->variables['APP_SERVICE_AUTHOR']],
|
||||
['email' => 'email']
|
||||
);
|
||||
|
||||
if ($validator->fails()) {
|
||||
foreach ($validator->errors()->all() as $error) {
|
||||
$this->output->error($error);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->output->comment(trans('command/messages.environment.app.app_url_help'));
|
||||
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask(
|
||||
trans('command/messages.environment.app.app_url'),
|
||||
|
|
|
@ -54,15 +54,15 @@ class InfoCommand extends Command
|
|||
$this->output->title('Version Information');
|
||||
$this->table([], [
|
||||
['Panel Version', $this->config->get('app.version')],
|
||||
['Latest Version', $this->versionService->getPanel()],
|
||||
['Latest Version', $this->versionService->getLatestPanel()],
|
||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
['Unique Identifier', $this->config->get('pterodactyl.service.author')],
|
||||
], 'compact');
|
||||
|
||||
$this->output->title('Application Configuration');
|
||||
$this->table([], [
|
||||
['Environment', $this->formatText($this->config->get('app.env'), $this->config->get('app.env') === 'production' ?: 'bg=red')],
|
||||
['Debug Mode', $this->formatText($this->config->get('app.debug') ? 'Yes' : 'No', !$this->config->get('app.debug') ?: 'bg=red')],
|
||||
['Environment', $this->formatText($this->config->get('app.env'), $this->config->get('app.env') === 'production' ? '' : 'bg=red')],
|
||||
['Debug Mode', $this->formatText($this->config->get('app.debug') ? 'Yes' : 'No', !$this->config->get('app.debug') ? '' : 'bg=red')],
|
||||
['Installation URL', $this->config->get('app.url')],
|
||||
['Installation Directory', base_path()],
|
||||
['Timezone', $this->config->get('app.timezone')],
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Console\Commands\Location;
|
||||
|
||||
|
@ -26,9 +19,9 @@ class DeleteLocationCommand extends Command
|
|||
protected $description = 'Deletes a location from the Panel.';
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Support\Collection
|
||||
* @var \Illuminate\Support\Collection|null
|
||||
*/
|
||||
protected $locations;
|
||||
protected $locations = null;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface
|
||||
|
|
|
@ -13,14 +13,14 @@ class SeedCommand extends BaseSeedCommand
|
|||
* Block someone from running this seed command if they have not completed
|
||||
* the migration process.
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
if (!$this->hasCompletedMigrations()) {
|
||||
$this->showMigrationWarning();
|
||||
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
return parent::handle();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,14 @@ class UpCommand extends BaseUpCommand
|
|||
* Block someone from running this up command if they have not completed
|
||||
* the migration process.
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
if (!$this->hasCompletedMigrations()) {
|
||||
$this->showMigrationWarning();
|
||||
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
return parent::handle();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class ProcessRunnableCommand extends Command
|
|||
/**
|
||||
* Handle command execution.
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$schedules = Schedule::query()->with('tasks')
|
||||
->where('is_active', true)
|
||||
|
@ -35,7 +35,7 @@ class ProcessRunnableCommand extends Command
|
|||
if ($schedules->count() < 1) {
|
||||
$this->line('There are no scheduled tasks for servers that need to be run.');
|
||||
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar(count($schedules));
|
||||
|
@ -47,6 +47,8 @@ class ProcessRunnableCommand extends Command
|
|||
}
|
||||
|
||||
$this->line('');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,7 +71,7 @@ class ProcessRunnableCommand extends Command
|
|||
'schedule' => $schedule->name,
|
||||
'hash' => $schedule->hashid,
|
||||
]));
|
||||
} catch (Throwable | Exception $exception) {
|
||||
} catch (Throwable|Exception $exception) {
|
||||
Log::error($exception, ['schedule_id' => $schedule->id]);
|
||||
|
||||
$this->error("An error was encountered while processing Schedule #{$schedule->id}: " . $exception->getMessage());
|
||||
|
|
|
@ -57,7 +57,7 @@ class UpgradeCommand extends Command
|
|||
$userDetails = posix_getpwuid(fileowner('public'));
|
||||
$user = $userDetails['name'] ?? 'www-data';
|
||||
|
||||
if (!$this->confirm("Your webserver user has been detected as [{$user}]: is this correct?", true)) {
|
||||
if (!$this->confirm("Your webserver user has been detected as <fg=blue>[{$user}]:</> is this correct?", true)) {
|
||||
$user = $this->anticipate(
|
||||
'Please enter the name of the user running your webserver process. This varies from system to system, but is generally "www-data", "nginx", or "apache".',
|
||||
[
|
||||
|
@ -73,7 +73,7 @@ class UpgradeCommand extends Command
|
|||
$groupDetails = posix_getgrgid(filegroup('public'));
|
||||
$group = $groupDetails['name'] ?? 'www-data';
|
||||
|
||||
if (!$this->confirm("Your webserver group has been detected as [{$group}]: is this correct?", true)) {
|
||||
if (!$this->confirm("Your webserver group has been detected as <fg=blue>[{$group}]:</> is this correct?", true)) {
|
||||
$group = $this->anticipate(
|
||||
'Please enter the name of the group running your webserver process. Normally this is the same as your user.',
|
||||
[
|
||||
|
@ -86,11 +86,13 @@ class UpgradeCommand extends Command
|
|||
}
|
||||
|
||||
if (!$this->confirm('Are you sure you want to run the upgrade process for your Panel?')) {
|
||||
$this->warn('Upgrade process terminated by user.');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ini_set('output_buffering', 0);
|
||||
ini_set('output_buffering', '0');
|
||||
$bar = $this->output->createProgressBar($skipDownload ? 9 : 10);
|
||||
$bar->start();
|
||||
|
||||
|
@ -173,8 +175,8 @@ class UpgradeCommand extends Command
|
|||
$this->call('up');
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info('Finished running upgrade.');
|
||||
$this->newLine(2);
|
||||
$this->info('Panel has been successfully upgraded. Please ensure you also update any Wings instances: https://pterodactyl.io/wings/1.0/upgrading.html');
|
||||
}
|
||||
|
||||
protected function withProgress(ProgressBar $bar, Closure $callback)
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Console\Commands\User;
|
||||
|
||||
|
@ -47,11 +40,9 @@ class DeleteUserCommand extends Command
|
|||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users'));
|
||||
Assert::notEmpty($search, 'Search term should be an email address, got: %s.');
|
||||
|
@ -68,13 +59,13 @@ class DeleteUserCommand extends Command
|
|||
return $this->handle();
|
||||
}
|
||||
|
||||
return false;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($this->input->isInteractive()) {
|
||||
$tableValues = [];
|
||||
foreach ($results as $user) {
|
||||
$tableValues[] = [$user->id, $user->email, $user->name];
|
||||
$tableValues[] = [$user->id, $user->email, $user->name_first];
|
||||
}
|
||||
|
||||
$this->table(['User ID', 'Email', 'Name'], $tableValues);
|
||||
|
@ -85,7 +76,7 @@ class DeleteUserCommand extends Command
|
|||
if (count($results) > 1) {
|
||||
$this->error(trans('command/messages.user.multiple_found'));
|
||||
|
||||
return false;
|
||||
return 1;
|
||||
}
|
||||
|
||||
$deleteUser = $results->first();
|
||||
|
@ -95,5 +86,7 @@ class DeleteUserCommand extends Command
|
|||
$this->deletionService->handle($deleteUser);
|
||||
$this->info(trans('command/messages.user.deleted'));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class MakeUserCommand extends Command
|
|||
['UUID', $user->uuid],
|
||||
['Email', $user->email],
|
||||
['Username', $user->username],
|
||||
['Name', $user->name],
|
||||
['Name', $user->name_first],
|
||||
['Admin', $user->root_admin ? 'Yes' : 'No'],
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -39,10 +39,8 @@ interface DatabaseRepositoryInterface extends RepositoryInterface
|
|||
|
||||
/**
|
||||
* Create a new database user on a given connection.
|
||||
*
|
||||
* @param $max_connections
|
||||
*/
|
||||
public function createUser(string $username, string $remote, string $password, string $max_connections): bool;
|
||||
public function createUser(string $username, string $remote, string $password, int $max_connections): bool;
|
||||
|
||||
/**
|
||||
* Give a specific user access to a given database.
|
||||
|
@ -61,8 +59,6 @@ interface DatabaseRepositoryInterface extends RepositoryInterface
|
|||
|
||||
/**
|
||||
* Drop a given user on a specific connection.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function dropUser(string $username, string $remote): bool;
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ interface LocationRepositoryInterface extends RepositoryInterface
|
|||
/**
|
||||
* Return all of the nodes and their respective count of servers for a location.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithNodes(int $id): Location;
|
||||
|
@ -29,8 +27,6 @@ interface LocationRepositoryInterface extends RepositoryInterface
|
|||
/**
|
||||
* Return a location and the count of nodes in that location.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithNodeCount(int $id): Location;
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Contracts\Repository;
|
||||
|
||||
|
@ -16,27 +9,7 @@ interface NestRepositoryInterface extends RepositoryInterface
|
|||
/**
|
||||
* Return a nest or all nests with their associated eggs and variables.
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithEggs(int $id = null);
|
||||
|
||||
/**
|
||||
* Return a nest or all nests and the count of eggs and servers for that nest.
|
||||
*
|
||||
* @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithCounts(int $id = null);
|
||||
|
||||
/**
|
||||
* Return a nest along with its associated eggs and the servers relation on those eggs.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithEggServers(int $id): Nest;
|
||||
public function getWithEggs(int $id = null): Nest;
|
||||
}
|
||||
|
|
|
@ -21,20 +21,20 @@ class Fractal extends SpatieFractal
|
|||
public function createData()
|
||||
{
|
||||
// Set the serializer by default.
|
||||
if (is_null($this->serializer)) {
|
||||
if (is_null($this->serializer)) { // @phpstan-ignore-line
|
||||
$this->serializer = new PterodactylSerializer();
|
||||
}
|
||||
|
||||
// Automatically set the paginator on the response object if the
|
||||
// data being provided implements a paginator.
|
||||
if (is_null($this->paginator) && $this->data instanceof LengthAwarePaginator) {
|
||||
if (is_null($this->paginator) && $this->data instanceof LengthAwarePaginator) { // @phpstan-ignore-line
|
||||
$this->paginator = new IlluminatePaginatorAdapter($this->data);
|
||||
}
|
||||
|
||||
// If the resource name is not set attempt to pull it off the transformer
|
||||
// itself and set it automatically.
|
||||
$class = is_string($this->transformer) ? new $this->transformer() : $this->transformer;
|
||||
if (is_null($this->resourceName) && $class instanceof Transformer) {
|
||||
if (is_null($this->resourceName) && $class instanceof Transformer) { // @phpstan-ignore-line
|
||||
$this->resourceName = $class->getResourceName();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,6 @@ final class Time
|
|||
{
|
||||
$offset = round(CarbonImmutable::now($timezone)->getTimezone()->getOffset(CarbonImmutable::now('UTC')) / 3600);
|
||||
|
||||
return sprintf('%s%s:00', $offset > 0 ? '+' : '-', str_pad(abs($offset), 2, '0', STR_PAD_LEFT));
|
||||
return sprintf('%s%s:00', $offset > 0 ? '+' : '-', str_pad((string) abs($offset), 2, '0', STR_PAD_LEFT));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class DatabaseController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetDatabasesRequest $request): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Application\Eggs;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Pterodactyl\Models\Nest;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Pterodactyl\Services\Eggs\Sharing\EggExporterService;
|
||||
use Pterodactyl\Transformers\Api\Application\EggTransformer;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggRequest;
|
||||
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||
|
@ -14,20 +16,31 @@ use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggsRequest;
|
|||
use Pterodactyl\Http\Requests\Api\Application\Eggs\StoreEggRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Eggs\DeleteEggRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Eggs\UpdateEggRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Eggs\ExportEggRequest;
|
||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
|
||||
class EggController extends ApplicationApiController
|
||||
{
|
||||
private EggExporterService $eggExporterService;
|
||||
|
||||
public function __construct(EggExporterService $eggExporterService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->eggExporterService = $eggExporterService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of all eggs on a given nest.
|
||||
*/
|
||||
public function index(GetEggsRequest $request, Nest $nest): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$eggs = QueryBuilder::for(Egg::query())
|
||||
->where('nest_id', '=', $nest->id)
|
||||
->allowedFilters(['id', 'name', 'author'])
|
||||
|
@ -56,11 +69,18 @@ class EggController extends ApplicationApiController
|
|||
*/
|
||||
public function store(StoreEggRequest $request): JsonResponse
|
||||
{
|
||||
$egg = Egg::query()->create($request->validated());
|
||||
$validated = $request->validated();
|
||||
$merged = array_merge($validated, [
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
// TODO: allow this to be set in the request, and default to config value if null or not present.
|
||||
'author' => config('pterodactyl.service.author'),
|
||||
]);
|
||||
|
||||
$egg = Egg::query()->create($merged);
|
||||
|
||||
return $this->fractal->item($egg)
|
||||
->transformWith(EggTransformer::class)
|
||||
->respond(JsonResponse::HTTP_CREATED);
|
||||
->respond(Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,4 +106,14 @@ class EggController extends ApplicationApiController
|
|||
|
||||
return $this->returnNoContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports an egg.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function export(ExportEggRequest $request, int $eggId): JsonResponse
|
||||
{
|
||||
return new JsonResponse($this->eggExporterService->handle($eggId));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Application\Eggs;
|
||||
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Services\Eggs\Variables\VariableUpdateService;
|
||||
use Pterodactyl\Services\Eggs\Variables\VariableCreationService;
|
||||
use Pterodactyl\Transformers\Api\Application\EggVariableTransformer;
|
||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Eggs\Variables\StoreEggVariableRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Eggs\Variables\UpdateEggVariablesRequest;
|
||||
|
||||
class EggVariableController extends ApplicationApiController
|
||||
{
|
||||
private ConnectionInterface $connection;
|
||||
|
||||
private VariableCreationService $variableCreationService;
|
||||
|
||||
private VariableUpdateService $variableUpdateService;
|
||||
|
||||
public function __construct(ConnectionInterface $connection, VariableCreationService $variableCreationService, VariableUpdateService $variableUpdateService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->connection = $connection;
|
||||
$this->variableCreationService = $variableCreationService;
|
||||
$this->variableUpdateService = $variableUpdateService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new egg variable.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
||||
*/
|
||||
public function store(StoreEggVariableRequest $request, Egg $egg): array
|
||||
{
|
||||
$variable = $this->variableCreationService->handle($egg->id, $request->validated());
|
||||
|
||||
return $this->fractal->item($variable)
|
||||
->transformWith(EggVariableTransformer::class)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates multiple egg variables.
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function update(UpdateEggVariablesRequest $request, Egg $egg): array
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$this->connection->transaction(function () use ($egg, $validated) {
|
||||
foreach ($validated as $data) {
|
||||
$this->variableUpdateService->handle($egg, $data);
|
||||
}
|
||||
});
|
||||
|
||||
return $this->fractal->collection($egg->refresh()->variables)
|
||||
->transformWith(EggVariableTransformer::class)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single egg variable.
|
||||
*/
|
||||
public function delete(Request $request, Egg $egg, EggVariable $eggVariable): Response
|
||||
{
|
||||
EggVariable::query()
|
||||
->where('id', $eggVariable->id)
|
||||
->where('egg_id', $egg->id)
|
||||
->delete();
|
||||
|
||||
return $this->returnNoContent();
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ class LocationController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetLocationsRequest $request): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class MountController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetMountsRequest $request): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ use Pterodactyl\Transformers\Api\Application\EggTransformer;
|
|||
use Pterodactyl\Transformers\Api\Application\NestTransformer;
|
||||
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Eggs\ImportEggRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nests\StoreNestRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteNestRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nests\UpdateNestRequest;
|
||||
|
@ -51,7 +51,7 @@ class NestController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetNestsRequest $request): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class AllocationController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetAllocationsRequest $request, Node $node): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class NodeController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetNodesRequest $request): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class NodeDeploymentController extends ApplicationApiController
|
|||
$nodes = $this->viableNodesService->setLocations($data['location_ids'] ?? [])
|
||||
->setMemory($data['memory'])
|
||||
->setDisk($data['disk'])
|
||||
->handle($request->query('per_page'), $request->query('page'));
|
||||
->handle($request->query('per_page'), $request->query('page')); // @phpstan-ignore-line
|
||||
|
||||
return $this->fractal->collection($nodes)
|
||||
->transformWith(NodeTransformer::class)
|
||||
|
|
|
@ -32,7 +32,7 @@ class RoleController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetRolesRequest $request): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class ServerController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetServersRequest $request): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ class ServerController extends ApplicationApiController
|
|||
*/
|
||||
public function store(StoreServerRequest $request): JsonResponse
|
||||
{
|
||||
$server = $this->creationService->handle($request->validated(), $request->getDeploymentObject());
|
||||
$server = $this->creationService->handle($request->validated());
|
||||
|
||||
return $this->fractal->item($server)
|
||||
->transformWith(ServerTransformer::class)
|
||||
|
|
|
@ -52,7 +52,7 @@ class UserController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetUsersRequest $request): array
|
||||
{
|
||||
$perPage = $request->query('per_page', 10);
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client;
|
|||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Auth\SessionGuard;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Pterodactyl\Services\Users\UserUpdateService;
|
||||
use Pterodactyl\Transformers\Api\Client\AccountTransformer;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest;
|
||||
|
@ -12,24 +12,26 @@ use Pterodactyl\Http\Requests\Api\Client\Account\UpdatePasswordRequest;
|
|||
|
||||
class AccountController extends ClientApiController
|
||||
{
|
||||
private SessionGuard $sessionGuard;
|
||||
private UserUpdateService $updateService;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Auth\AuthManager
|
||||
*/
|
||||
private $sessionGuard;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
*/
|
||||
public function __construct(SessionGuard $sessionGuard, UserUpdateService $updateService)
|
||||
public function __construct(UserUpdateService $updateService, AuthManager $sessionGuard)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->sessionGuard = $sessionGuard;
|
||||
$this->updateService = $updateService;
|
||||
$this->sessionGuard = $sessionGuard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the currently authenticated user.
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function index(Request $request): array
|
||||
{
|
||||
|
@ -40,9 +42,6 @@ class AccountController extends ClientApiController
|
|||
|
||||
/**
|
||||
* Update the authenticated user's email address.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function updateEmail(UpdateEmailRequest $request): Response
|
||||
{
|
||||
|
@ -65,9 +64,12 @@ class AccountController extends ClientApiController
|
|||
// cached copy of the user that does not include the updated password. Do this
|
||||
// to correctly store the new user details in the guard and allow the logout
|
||||
// other devices functionality to work.
|
||||
$this->sessionGuard->setUser($user);
|
||||
if (method_exists($this->sessionGuard, 'setUser')) {
|
||||
$this->sessionGuard->setUser($user);
|
||||
}
|
||||
|
||||
$this->sessionGuard->logoutOtherDevices($request->input('password'));
|
||||
// TODO: Find another way to do this, function doesn't exist due to API changes.
|
||||
//$this->sessionGuard->logoutOtherDevices($request->input('password'));
|
||||
|
||||
return $this->returnNoContent();
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ class ClientController extends ClientApiController
|
|||
$builder = $builder->whereIn('servers.id', $user->accessibleServers()->pluck('id')->all());
|
||||
}
|
||||
|
||||
$servers = $builder->paginate(min($request->query('per_page', 50), 100))->appends($request->query());
|
||||
$servers = $builder->paginate(min((int) $request->query('per_page', '50'), 100))->appends($request->query());
|
||||
|
||||
return $this->fractal->transformWith(new ServerTransformer())->collection($servers)->toArray();
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ class StartupController extends ClientApiController
|
|||
*/
|
||||
public function update(UpdateStartupVariableRequest $request, Server $server): array
|
||||
{
|
||||
/** @var \Pterodactyl\Models\EggVariable $variable */
|
||||
$variable = $server->variables()->where('env_variable', $request->input('key'))->first();
|
||||
|
||||
if (is_null($variable) || !$variable->user_viewable) {
|
||||
|
|
|
@ -7,7 +7,7 @@ use Pterodactyl\Models\User;
|
|||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Auth\Events\Failed;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Illuminate\Container\Container;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
@ -17,6 +17,8 @@ abstract class AbstractLoginController extends Controller
|
|||
{
|
||||
use AuthenticatesUsers;
|
||||
|
||||
protected AuthManager $auth;
|
||||
|
||||
/**
|
||||
* Lockout time for failed login requests.
|
||||
*
|
||||
|
@ -38,26 +40,14 @@ abstract class AbstractLoginController extends Controller
|
|||
*/
|
||||
protected $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Auth\AuthManager
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* LoginController constructor.
|
||||
*/
|
||||
public function __construct(AuthManager $auth, Repository $config)
|
||||
public function __construct()
|
||||
{
|
||||
$this->lockoutTime = $config->get('auth.lockout.time');
|
||||
$this->maxLoginAttempts = $config->get('auth.lockout.attempts');
|
||||
|
||||
$this->auth = $auth;
|
||||
$this->config = $config;
|
||||
$this->lockoutTime = config('auth.lockout.time');
|
||||
$this->maxLoginAttempts = config('auth.lockout.attempts');
|
||||
$this->auth = Container::getInstance()->make(AuthManager::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,7 +62,7 @@ abstract class AbstractLoginController extends Controller
|
|||
$this->getField($request->input('user')) => $request->input('user'),
|
||||
]);
|
||||
|
||||
if ($request->route()->named('auth.login-checkpoint')) {
|
||||
if ($request->route()->named('auth.checkpoint') || $request->route()->named('auth.checkpoint.key')) {
|
||||
throw new DisplayException($message ?? trans('auth.two_factor.checkpoint_failed'));
|
||||
}
|
||||
|
||||
|
@ -84,7 +74,9 @@ abstract class AbstractLoginController extends Controller
|
|||
*/
|
||||
protected function sendLoginResponse(User $user, Request $request): JsonResponse
|
||||
{
|
||||
$request->session()->remove('auth_confirmation_token');
|
||||
$request->session()->regenerate();
|
||||
|
||||
$this->clearLoginAttempts($request);
|
||||
|
||||
$this->auth->guard()->login($user, true);
|
||||
|
@ -99,8 +91,6 @@ abstract class AbstractLoginController extends Controller
|
|||
|
||||
/**
|
||||
* Determine if the user is logging in using an email or username,.
|
||||
*
|
||||
* @param string $input
|
||||
*/
|
||||
protected function getField(string $input = null): string
|
||||
{
|
||||
|
|
|
@ -16,7 +16,6 @@ class ForgotPasswordController extends Controller
|
|||
/**
|
||||
* Get the response for a failed password reset link.
|
||||
*
|
||||
* @param \Illuminate\Http\Request
|
||||
* @param string $response
|
||||
*/
|
||||
protected function sendResetLinkFailedResponse(Request $request, $response): JsonResponse
|
||||
|
|
|
@ -2,36 +2,35 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\Auth;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Carbon\CarbonInterface;
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest;
|
||||
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
||||
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
|
||||
|
||||
class LoginCheckpointController extends AbstractLoginController
|
||||
{
|
||||
private CacheRepository $cache;
|
||||
public const TOKEN_EXPIRED_MESSAGE = 'The authentication token provided has expired, please refresh the page and try again.';
|
||||
|
||||
private Encrypter $encrypter;
|
||||
|
||||
private Google2FA $google2FA;
|
||||
|
||||
private ValidationFactory $validation;
|
||||
|
||||
/**
|
||||
* LoginCheckpointController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
AuthManager $auth,
|
||||
Repository $config,
|
||||
CacheRepository $cache,
|
||||
Encrypter $encrypter,
|
||||
Google2FA $google2FA
|
||||
) {
|
||||
parent::__construct($auth, $config);
|
||||
public function __construct(Encrypter $encrypter, Google2FA $google2FA, ValidationFactory $validation)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->cache = $cache;
|
||||
$this->encrypter = $encrypter;
|
||||
$this->google2FA = $google2FA;
|
||||
$this->validation = $validation;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,6 +44,7 @@ class LoginCheckpointController extends AbstractLoginController
|
|||
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
*/
|
||||
public function __invoke(LoginCheckpointRequest $request)
|
||||
{
|
||||
|
@ -54,18 +54,24 @@ class LoginCheckpointController extends AbstractLoginController
|
|||
return;
|
||||
}
|
||||
|
||||
$token = $request->input('confirmation_token');
|
||||
$details = $request->session()->get('auth_confirmation_token');
|
||||
if (!$this->hasValidSessionData($details)) {
|
||||
$this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hash_equals($request->input('confirmation_token') ?? '', $details['token_value'])) {
|
||||
$this->sendFailedLoginResponse($request);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
$user = User::query()->findOrFail($this->cache->get($token, 0));
|
||||
$user = User::query()->findOrFail($details['user_id']);
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
$this->incrementLoginAttempts($request);
|
||||
|
||||
$this->sendFailedLoginResponse(
|
||||
$request,
|
||||
null,
|
||||
'The authentication token provided has expired, please refresh the page and try again.'
|
||||
);
|
||||
$this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -79,25 +85,18 @@ class LoginCheckpointController extends AbstractLoginController
|
|||
$decrypted = $this->encrypter->decrypt($user->totp_secret);
|
||||
|
||||
if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) {
|
||||
$this->cache->delete($token);
|
||||
|
||||
return $this->sendLoginResponse($user, $request);
|
||||
}
|
||||
}
|
||||
|
||||
$this->incrementLoginAttempts($request);
|
||||
$this->sendFailedLoginResponse($request, $user, !empty($recoveryToken) ? 'The recovery token provided is not valid.' : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given recovery token is valid for the user account. If we find a matching token
|
||||
* it will be deleted from the database.
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function isValidRecoveryToken(User $user, string $value)
|
||||
protected function isValidRecoveryToken(User $user, string $value): bool
|
||||
{
|
||||
foreach ($user->recoveryTokens as $token) {
|
||||
if (password_verify($value, $token->token)) {
|
||||
|
@ -109,4 +108,37 @@ class LoginCheckpointController extends AbstractLoginController
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function hasValidSessionData(array $data): bool
|
||||
{
|
||||
return static::isValidSessionData($this->validation, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the data provided from the session is valid or not. This
|
||||
* will return false if the data is invalid, or if more time has passed than
|
||||
* was configured when the session was written.
|
||||
*/
|
||||
public static function isValidSessionData(ValidationFactory $validation, array $data): bool
|
||||
{
|
||||
$validator = $validation->make($data, [
|
||||
'user_id' => 'required|integer|min:1',
|
||||
'token_value' => 'required|string',
|
||||
'expires_at' => 'required',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$data['expires_at'] instanceof CarbonInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data['expires_at']->isBefore(CarbonImmutable::now())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,11 @@ namespace Pterodactyl\Http\Controllers\Auth;
|
|||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use LaravelWebauthn\Facades\Webauthn;
|
||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
||||
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
|
||||
class LoginController extends AbstractLoginController
|
||||
{
|
||||
|
@ -21,31 +18,22 @@ class LoginController extends AbstractLoginController
|
|||
private const METHOD_TOTP = 'totp';
|
||||
private const METHOD_WEBAUTHN = 'webauthn';
|
||||
|
||||
private CacheRepository $cache;
|
||||
private UserRepositoryInterface $repository;
|
||||
private ViewFactory $view;
|
||||
|
||||
/**
|
||||
* LoginController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
AuthManager $auth,
|
||||
Repository $config,
|
||||
CacheRepository $cache,
|
||||
UserRepositoryInterface $repository,
|
||||
ViewFactory $view
|
||||
) {
|
||||
parent::__construct($auth, $config);
|
||||
public function __construct(ViewFactory $view)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->cache = $cache;
|
||||
$this->repository = $repository;
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all incoming requests for the authentication routes and render the
|
||||
* base authentication view component. React will take over at this point and
|
||||
* turn the login area into a SPA.
|
||||
* base authentication view component. React will take over at this point and
|
||||
* turn the login area into an SPA.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
|
@ -62,9 +50,6 @@ class LoginController extends AbstractLoginController
|
|||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$username = $request->input('user');
|
||||
$useColumn = $this->getField($username);
|
||||
|
||||
if ($this->hasTooManyLoginAttempts($request)) {
|
||||
$this->fireLockoutEvent($request);
|
||||
$this->sendLockoutResponse($request);
|
||||
|
@ -72,13 +57,12 @@ class LoginController extends AbstractLoginController
|
|||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
$user = $this->repository->findFirstWhere([[$useColumn, '=', $username]]);
|
||||
} catch (RecordNotFoundException $exception) {
|
||||
$this->sendFailedLoginResponse($request);
|
||||
$username = $request->input('user');
|
||||
|
||||
return;
|
||||
/** @var \Pterodactyl\Models\User|null $user */
|
||||
$user = User::query()->where($this->getField($username), $username)->first();
|
||||
if (is_null($user)) {
|
||||
$this->sendFailedLoginResponse($request);
|
||||
}
|
||||
|
||||
// Ensure that the account is using a valid username and password before trying to
|
||||
|
@ -91,17 +75,44 @@ class LoginController extends AbstractLoginController
|
|||
return;
|
||||
}
|
||||
|
||||
if ($user->use_totp) {
|
||||
$token = Str::random(64);
|
||||
$this->cache->put($token, $user->id, CarbonImmutable::now()->addMinutes(5));
|
||||
$useTotp = $user->use_totp;
|
||||
$webauthnKeys = $user->webauthnKeys()->get();
|
||||
|
||||
return new JsonResponse([
|
||||
'complete' => false,
|
||||
'methods' => [self::METHOD_TOTP],
|
||||
'confirmation_token' => $token,
|
||||
]);
|
||||
if (!$useTotp && count($webauthnKeys) < 1) {
|
||||
return $this->sendLoginResponse($user, $request);
|
||||
}
|
||||
|
||||
return $this->sendLoginResponse($user, $request);
|
||||
$methods = [];
|
||||
if ($useTotp) {
|
||||
$methods[] = self::METHOD_TOTP;
|
||||
}
|
||||
if (count($webauthnKeys) > 0) {
|
||||
$methods[] = self::METHOD_WEBAUTHN;
|
||||
}
|
||||
|
||||
$token = Str::random(64);
|
||||
|
||||
$request->session()->put('auth_confirmation_token', [
|
||||
'user_id' => $user->id,
|
||||
'token_value' => $token,
|
||||
'expires_at' => CarbonImmutable::now()->addMinutes(5),
|
||||
]);
|
||||
|
||||
$response = [
|
||||
'complete' => false,
|
||||
'methods' => $methods,
|
||||
'confirmation_token' => $token,
|
||||
];
|
||||
|
||||
if (count($webauthnKeys) > 0) {
|
||||
$publicKey = Webauthn::getAuthenticateData($user);
|
||||
$request->session()->put(self::SESSION_PUBLICKEY_REQUEST, $publicKey);
|
||||
|
||||
$response['webauthn'] = [
|
||||
'public_key' => $publicKey,
|
||||
];
|
||||
}
|
||||
|
||||
return new JsonResponse($response);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ class ResetPasswordController extends Controller
|
|||
* account do not automatically log them in. In those cases, send the user back to the login
|
||||
* form with a note telling them their password was changed and to log back in.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\CanResetPassword|\Pterodactyl\Models\User $user
|
||||
* @param \Pterodactyl\Models\User $user
|
||||
* @param string $password
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
|
|
|
@ -54,7 +54,7 @@ class SubstituteApplicationApiBindings
|
|||
try {
|
||||
$this->router->substituteImplicitBindings($route = $request->route());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
if (isset($route) && $route->getMissing()) {
|
||||
if (!empty($route) && $route->getMissing()) {
|
||||
$route->getMissing()($request);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class SubstituteClientApiBindings
|
|||
/**
|
||||
* Perform substitution of route parameters for the Client API.
|
||||
*
|
||||
* @param \Illuminate\Http\Request
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -76,7 +76,7 @@ class SubstituteClientApiBindings
|
|||
/* @var \Illuminate\Routing\Route $route */
|
||||
$this->router->substituteBindings($route = $request->route());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
if (isset($route) && $route->getMissing()) {
|
||||
if (!empty($route) && $route->getMissing()) {
|
||||
$route->getMissing()($request);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class RequireTwoFactorAuthentication
|
|||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
/** @var \Pterodactyl\Models\User|null $user */
|
||||
$user = $request->user();
|
||||
$uri = rtrim($request->getRequestUri(), '/') . '/';
|
||||
$current = $request->route()->getName();
|
||||
|
@ -66,6 +66,7 @@ class RequireTwoFactorAuthentication
|
|||
throw new TwoFactorAuthRequiredException();
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash();
|
||||
|
||||
return redirect()->to($this->redirectRoute);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
|
||||
|
||||
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
|
||||
class ExportEggRequest extends ApplicationApiRequest
|
||||
{
|
||||
}
|
|
@ -2,13 +2,29 @@
|
|||
|
||||
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
|
||||
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
|
||||
class StoreEggRequest extends ApplicationApiRequest
|
||||
{
|
||||
public function rules(array $rules = null): array
|
||||
{
|
||||
return $rules ?? Egg::getRules();
|
||||
return [
|
||||
'nest_id' => 'required|bail|numeric|exists:nests,id',
|
||||
'name' => 'required|string|max:191',
|
||||
'description' => 'sometimes|string|nullable',
|
||||
'features' => 'sometimes|array',
|
||||
'docker_images' => 'required|array|min:1',
|
||||
'docker_images.*' => 'required|string',
|
||||
'file_denylist' => 'sometimes|array|nullable',
|
||||
'file_denylist.*' => 'sometimes|string',
|
||||
'config_files' => 'required|nullable|json',
|
||||
'config_startup' => 'required|nullable|json',
|
||||
'config_stop' => 'required|nullable|string|max:191',
|
||||
// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id',
|
||||
'startup' => 'required|string',
|
||||
'script_container' => 'sometimes|string',
|
||||
'script_entry' => 'sometimes|string',
|
||||
'script_install' => 'sometimes|string',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,16 +10,16 @@ class UpdateEggRequest extends StoreEggRequest
|
|||
'nest_id' => 'sometimes|numeric|exists:nests,id',
|
||||
'name' => 'sometimes|string|max:191',
|
||||
'description' => 'sometimes|string|nullable',
|
||||
'features' => 'sometimes|array|nullable',
|
||||
'docker_images' => 'sometimes|required|array|min:1',
|
||||
'features' => 'sometimes|array',
|
||||
'docker_images' => 'sometimes|array|min:1',
|
||||
'docker_images.*' => 'sometimes|string',
|
||||
'file_denylist' => 'sometimes|array|nullable',
|
||||
'file_denylist.*' => 'sometimes|string',
|
||||
'config_files' => 'sometimes|nullable|json',
|
||||
'config_startup' => 'sometimes|nullable|json',
|
||||
'config_stop' => 'sometimes|nullable|string|max:191',
|
||||
'config_from' => 'sometimes|nullable|numeric|exists:eggs,id',
|
||||
'startup' => 'sometimes|nullable|string',
|
||||
// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id',
|
||||
'startup' => 'sometimes|string',
|
||||
'script_container' => 'sometimes|string',
|
||||
'script_entry' => 'sometimes|string',
|
||||
'script_install' => 'sometimes|string',
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Requests\Api\Application\Eggs\Variables;
|
||||
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
|
||||
class StoreEggVariableRequest extends ApplicationApiRequest
|
||||
{
|
||||
public function rules(array $rules = null): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|min:1|max:191',
|
||||
'description' => 'sometimes|string|nullable',
|
||||
'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES,
|
||||
'default_value' => 'present',
|
||||
'user_viewable' => 'required|boolean',
|
||||
'user_editable' => 'required|boolean',
|
||||
'rules' => 'bail|required|string',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Requests\Api\Application\Eggs\Variables;
|
||||
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
|
||||
class UpdateEggVariablesRequest extends ApplicationApiRequest
|
||||
{
|
||||
public function rules(array $rules = null): array
|
||||
{
|
||||
return [
|
||||
'*' => 'array',
|
||||
'*.id' => 'required|integer',
|
||||
'*.name' => 'sometimes|string|min:1|max:191',
|
||||
'*.description' => 'sometimes|string|nullable',
|
||||
'*.env_variable' => 'sometimes|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES,
|
||||
'*.default_value' => 'sometimes|present',
|
||||
'*.user_viewable' => 'sometimes|boolean',
|
||||
'*.user_editable' => 'sometimes|boolean',
|
||||
'*.rules' => 'sometimes|string',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -3,9 +3,6 @@
|
|||
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
|
||||
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Pterodactyl\Models\Objects\DeploymentObject;
|
||||
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
|
||||
class StoreServerRequest extends ApplicationApiRequest
|
||||
|
@ -18,15 +15,9 @@ class StoreServerRequest extends ApplicationApiRequest
|
|||
'external_id' => $rules['external_id'],
|
||||
'name' => $rules['name'],
|
||||
'description' => array_merge(['nullable'], $rules['description']),
|
||||
'user' => $rules['owner_id'],
|
||||
'egg' => $rules['egg_id'],
|
||||
'docker_image' => $rules['image'],
|
||||
'startup' => $rules['startup'],
|
||||
'environment' => 'present|array',
|
||||
'skip_scripts' => 'sometimes|boolean',
|
||||
'oom_disabled' => 'sometimes|boolean',
|
||||
'owner_id' => $rules['owner_id'],
|
||||
'node_id' => $rules['node_id'],
|
||||
|
||||
// Resource limitations
|
||||
'limits' => 'required|array',
|
||||
'limits.memory' => $rules['memory'],
|
||||
'limits.swap' => $rules['swap'],
|
||||
|
@ -34,26 +25,21 @@ class StoreServerRequest extends ApplicationApiRequest
|
|||
'limits.io' => $rules['io'],
|
||||
'limits.threads' => $rules['threads'],
|
||||
'limits.cpu' => $rules['cpu'],
|
||||
'limits.oom_killer' => 'required|boolean',
|
||||
|
||||
// Application Resource Limits
|
||||
'feature_limits' => 'required|array',
|
||||
'feature_limits.databases' => $rules['database_limit'],
|
||||
'feature_limits.allocations' => $rules['allocation_limit'],
|
||||
'feature_limits.backups' => $rules['backup_limit'],
|
||||
'feature_limits.databases' => $rules['database_limit'],
|
||||
|
||||
// Placeholders for rules added in withValidator() function.
|
||||
'allocation.default' => '',
|
||||
'allocation.additional.*' => '',
|
||||
'allocation.default' => 'required|bail|integer|exists:allocations,id',
|
||||
'allocation.additional.*' => 'integer|exists:allocations,id',
|
||||
|
||||
// Automatic deployment rules
|
||||
'deploy' => 'sometimes|required|array',
|
||||
'deploy.locations' => 'array',
|
||||
'deploy.locations.*' => 'integer|min:1',
|
||||
'deploy.dedicated_ip' => 'required_with:deploy,boolean',
|
||||
'deploy.port_range' => 'array',
|
||||
'deploy.port_range.*' => 'string',
|
||||
|
||||
'start_on_completion' => 'sometimes|boolean',
|
||||
'startup' => $rules['startup'],
|
||||
'environment' => 'present|array',
|
||||
'egg_id' => $rules['egg_id'],
|
||||
'image' => $rules['image'],
|
||||
'skip_scripts' => 'present|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -65,69 +51,30 @@ class StoreServerRequest extends ApplicationApiRequest
|
|||
'external_id' => array_get($data, 'external_id'),
|
||||
'name' => array_get($data, 'name'),
|
||||
'description' => array_get($data, 'description'),
|
||||
'owner_id' => array_get($data, 'user'),
|
||||
'egg_id' => array_get($data, 'egg'),
|
||||
'image' => array_get($data, 'docker_image'),
|
||||
'startup' => array_get($data, 'startup'),
|
||||
'environment' => array_get($data, 'environment'),
|
||||
'owner_id' => array_get($data, 'owner_id'),
|
||||
'node_id' => array_get($data, 'node_id'),
|
||||
|
||||
'memory' => array_get($data, 'limits.memory'),
|
||||
'swap' => array_get($data, 'limits.swap'),
|
||||
'disk' => array_get($data, 'limits.disk'),
|
||||
'io' => array_get($data, 'limits.io'),
|
||||
'cpu' => array_get($data, 'limits.cpu'),
|
||||
'threads' => array_get($data, 'limits.threads'),
|
||||
'skip_scripts' => array_get($data, 'skip_scripts', false),
|
||||
'allocation_id' => array_get($data, 'allocation.default'),
|
||||
'allocation_additional' => array_get($data, 'allocation.additional'),
|
||||
'start_on_completion' => array_get($data, 'start_on_completion', false),
|
||||
'database_limit' => array_get($data, 'feature_limits.databases'),
|
||||
'cpu' => array_get($data, 'limits.cpu'),
|
||||
'oom_disabled' => !array_get($data, 'limits.oom_killer'),
|
||||
|
||||
'allocation_limit' => array_get($data, 'feature_limits.allocations'),
|
||||
'backup_limit' => array_get($data, 'feature_limits.backups'),
|
||||
'database_limit' => array_get($data, 'feature_limits.databases'),
|
||||
|
||||
'allocation_id' => array_get($data, 'allocation.default'),
|
||||
'allocation_additional' => array_get($data, 'allocation.additional'),
|
||||
|
||||
'startup' => array_get($data, 'startup'),
|
||||
'environment' => array_get($data, 'environment'),
|
||||
'egg_id' => array_get($data, 'egg_id'),
|
||||
'image' => array_get($data, 'image'),
|
||||
'skip_scripts' => array_get($data, 'skip_scripts'),
|
||||
'start_on_completion' => array_get($data, 'start_on_completion', false),
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator)
|
||||
{
|
||||
$validator->sometimes('allocation.default', [
|
||||
'required',
|
||||
'integer',
|
||||
'bail',
|
||||
Rule::exists('allocations', 'id')->where(function ($query) {
|
||||
$query->whereNull('server_id');
|
||||
}),
|
||||
], function ($input) {
|
||||
return !($input->deploy);
|
||||
});
|
||||
|
||||
$validator->sometimes('allocation.additional.*', [
|
||||
'integer',
|
||||
Rule::exists('allocations', 'id')->where(function ($query) {
|
||||
$query->whereNull('server_id');
|
||||
}),
|
||||
], function ($input) {
|
||||
return !($input->deploy);
|
||||
});
|
||||
|
||||
$validator->sometimes('deploy.locations', 'present', function ($input) {
|
||||
return $input->deploy;
|
||||
});
|
||||
|
||||
$validator->sometimes('deploy.port_range', 'present', function ($input) {
|
||||
return $input->deploy;
|
||||
});
|
||||
}
|
||||
|
||||
public function getDeploymentObject(): ?DeploymentObject
|
||||
{
|
||||
if (is_null($this->input('deploy'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$object = new DeploymentObject();
|
||||
$object->setDedicated($this->input('deploy.dedicated_ip', false));
|
||||
$object->setLocations($this->input('deploy.locations', []));
|
||||
$object->setPorts($this->input('deploy.port_range', []));
|
||||
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class UpdateServerRequest extends ApplicationApiRequest
|
|||
'io' => array_get($data, 'limits.io'),
|
||||
'threads' => array_get($data, 'limits.threads'),
|
||||
'cpu' => array_get($data, 'limits.cpu'),
|
||||
'oom_disabled' => array_get($data, 'limits.oom_disabled'),
|
||||
'oom_disabled' => !array_get($data, 'limits.oom_killer'),
|
||||
|
||||
'allocation_limit' => array_get($data, 'feature_limits.allocations'),
|
||||
'backup_limit' => array_get($data, 'feature_limits.backups'),
|
||||
|
|
|
@ -65,8 +65,6 @@ abstract class SubuserRequest extends ClientApiRequest
|
|||
// Otherwise, get the current subuser's permission set, and ensure that the
|
||||
// permissions they are trying to assign are not _more_ than the ones they
|
||||
// already have.
|
||||
/** @var \Pterodactyl\Models\Subuser|null $subuser */
|
||||
/** @var \Pterodactyl\Services\Servers\GetUserPermissionsService $service */
|
||||
$service = $this->container->make(GetUserPermissionsService::class);
|
||||
|
||||
if (count(array_diff($permissions, $service->handle($server, $user))) > 0) {
|
||||
|
|
|
@ -101,7 +101,7 @@ class AuditLog extends Model
|
|||
* currently authenticated user if available. This model is not saved at this point, so
|
||||
* you can always make modifications to it as needed before saving.
|
||||
*
|
||||
* @return $this
|
||||
* @return self
|
||||
*/
|
||||
public static function instance(string $action, array $metadata, bool $isSystem = false)
|
||||
{
|
||||
|
|
|
@ -2,17 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $host
|
||||
* @property int $port
|
||||
* @property string $username
|
||||
* @property string $password
|
||||
* @property int|null $max_databases
|
||||
* @property \Carbon\CarbonImmutable $created_at
|
||||
* @property \Carbon\CarbonImmutable $updated_at
|
||||
*/
|
||||
class DatabaseHost extends Model
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -2,46 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $uuid
|
||||
* @property int $nest_id
|
||||
* @property string $author
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property array|null $features
|
||||
* @property string $docker_image -- deprecated, use $docker_images
|
||||
* @property string $update_url
|
||||
* @property array $docker_images
|
||||
* @property array|null $file_denylist
|
||||
* @property string|null $config_files
|
||||
* @property string|null $config_startup
|
||||
* @property string|null $config_logs
|
||||
* @property string|null $config_stop
|
||||
* @property int|null $config_from
|
||||
* @property string|null $startup
|
||||
* @property bool $script_is_privileged
|
||||
* @property string|null $script_install
|
||||
* @property string $script_entry
|
||||
* @property string $script_container
|
||||
* @property int|null $copy_script_from
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property string|null $copy_script_install
|
||||
* @property string $copy_script_entry
|
||||
* @property string $copy_script_container
|
||||
* @property string|null $inherit_config_files
|
||||
* @property string|null $inherit_config_startup
|
||||
* @property string|null $inherit_config_logs
|
||||
* @property string|null $inherit_config_stop
|
||||
* @property string $inherit_file_denylist
|
||||
* @property array|null $inherit_features
|
||||
* @property \Pterodactyl\Models\Nest $nest
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\EggVariable[] $variables
|
||||
* @property \Pterodactyl\Models\Egg|null $scriptFrom
|
||||
* @property \Pterodactyl\Models\Egg|null $configFrom
|
||||
*/
|
||||
class Egg extends Model
|
||||
{
|
||||
/**
|
||||
|
@ -75,14 +35,16 @@ class Egg extends Model
|
|||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'nest_id',
|
||||
'uuid',
|
||||
'name',
|
||||
'description',
|
||||
'features',
|
||||
'author',
|
||||
'docker_images',
|
||||
'file_denylist',
|
||||
'config_files',
|
||||
'config_startup',
|
||||
'config_logs',
|
||||
'config_stop',
|
||||
'config_from',
|
||||
'startup',
|
||||
|
@ -123,7 +85,6 @@ class Egg extends Model
|
|||
'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id',
|
||||
'config_stop' => 'required_without:config_from|nullable|string|max:191',
|
||||
'config_startup' => 'required_without:config_from|nullable|json',
|
||||
'config_logs' => 'required_without:config_from|nullable|json',
|
||||
'config_files' => 'required_without:config_from|nullable|json',
|
||||
'update_url' => 'sometimes|nullable|string',
|
||||
];
|
||||
|
@ -136,7 +97,6 @@ class Egg extends Model
|
|||
'file_denylist' => null,
|
||||
'config_stop' => null,
|
||||
'config_startup' => null,
|
||||
'config_logs' => null,
|
||||
'config_files' => null,
|
||||
'update_url' => null,
|
||||
];
|
||||
|
@ -164,10 +124,12 @@ class Egg extends Model
|
|||
*/
|
||||
public function getCopyScriptEntryAttribute()
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
if (!is_null($this->script_entry) || is_null($this->copy_script_from)) {
|
||||
return $this->script_entry;
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->scriptFrom->script_entry;
|
||||
}
|
||||
|
||||
|
@ -179,10 +141,12 @@ class Egg extends Model
|
|||
*/
|
||||
public function getCopyScriptContainerAttribute()
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
if (!is_null($this->script_container) || is_null($this->copy_script_from)) {
|
||||
return $this->script_container;
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->scriptFrom->script_container;
|
||||
}
|
||||
|
||||
|
@ -214,20 +178,6 @@ class Egg extends Model
|
|||
return $this->configFrom->config_startup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the log reading configuration for an egg.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInheritConfigLogsAttribute()
|
||||
{
|
||||
if (!is_null($this->config_logs) || is_null($this->config_from)) {
|
||||
return $this->config_logs;
|
||||
}
|
||||
|
||||
return $this->configFrom->config_logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the stop command configuration for an egg.
|
||||
*
|
||||
|
|
|
@ -2,26 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $egg_id
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property string $env_variable
|
||||
* @property string $default_value
|
||||
* @property bool $user_viewable
|
||||
* @property bool $user_editable
|
||||
* @property string $rules
|
||||
* @property \Carbon\CarbonImmutable $created_at
|
||||
* @property \Carbon\CarbonImmutable $updated_at
|
||||
* @property bool $required
|
||||
* @property \Pterodactyl\Models\Egg $egg
|
||||
* @property \Pterodactyl\Models\ServerVariable $serverVariable
|
||||
*
|
||||
* The "server_value" variable is only present on the object if you've loaded this model
|
||||
* using the server relationship.
|
||||
* @property string|null $server_value
|
||||
*/
|
||||
class EggVariable extends Model
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -48,6 +48,7 @@ class MultiFieldServerFilter implements Filter
|
|||
}
|
||||
},
|
||||
// Otherwise, just try to search for that specific port in the allocations.
|
||||
// @phpstan-ignore-next-line
|
||||
function (Builder $builder) use ($value) {
|
||||
$builder->orWhere('allocations.port', 'LIKE', substr($value, 1) . '%');
|
||||
}
|
||||
|
|
|
@ -23,22 +23,15 @@ abstract class Model extends IlluminateModel
|
|||
/**
|
||||
* Determines if the model should undergo data validation before it is saved
|
||||
* to the database.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $skipValidation = false;
|
||||
protected bool $skipValidation = false;
|
||||
|
||||
/**
|
||||
* The validator instance used by this model.
|
||||
*
|
||||
* @var \Illuminate\Validation\Validator
|
||||
*/
|
||||
protected $validator;
|
||||
protected ?Validator $validator = null;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Validation\Factory
|
||||
*/
|
||||
protected static $validatorFactory;
|
||||
protected static Factory $validatorFactory;
|
||||
|
||||
public static array $validationRules = [];
|
||||
|
||||
|
@ -82,6 +75,7 @@ abstract class Model extends IlluminateModel
|
|||
{
|
||||
$rules = $this->getKey() ? static::getRulesForUpdate($this) : static::getRules();
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->validator ?: $this->validator = static::$validatorFactory->make(
|
||||
[],
|
||||
$rules,
|
||||
|
|
|
@ -8,38 +8,6 @@ use Illuminate\Container\Container;
|
|||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $uuid
|
||||
* @property bool $public
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property int $location_id
|
||||
* @property int|null $database_host_id
|
||||
* @property string $fqdn
|
||||
* @property int $listen_port_http
|
||||
* @property int $public_port_http
|
||||
* @property int $listen_port_sftp
|
||||
* @property int $public_port_sftp
|
||||
* @property string $scheme
|
||||
* @property bool $behind_proxy
|
||||
* @property bool $maintenance_mode
|
||||
* @property int $memory
|
||||
* @property int $memory_overallocate
|
||||
* @property int $disk
|
||||
* @property int $disk_overallocate
|
||||
* @property int $upload_size
|
||||
* @property string $daemon_token_id
|
||||
* @property string $daemon_token
|
||||
* @property string $daemon_base
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property \Pterodactyl\Models\Location $location
|
||||
* @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts
|
||||
* @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers
|
||||
* @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations
|
||||
* @property \Pterodactyl\Models\DatabaseHost $databaseHost
|
||||
*/
|
||||
class Node extends Model
|
||||
{
|
||||
use Notifiable;
|
||||
|
@ -275,6 +243,7 @@ class Node extends Model
|
|||
$memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100));
|
||||
$diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100));
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@ class Permission extends Model
|
|||
* Returns all of the permissions available on the system for a user to
|
||||
* have when controlling a server.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
* @phpstan-return \Illuminate\Support\Collection<string, array{description: string, keys: array<string, string>}>
|
||||
*/
|
||||
public static function permissions(): Collection
|
||||
{
|
||||
|
|
|
@ -8,50 +8,6 @@ use Illuminate\Database\Query\JoinClause;
|
|||
use Znck\Eloquent\Traits\BelongsToThrough;
|
||||
use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string|null $external_id
|
||||
* @property string $uuid
|
||||
* @property string $uuidShort
|
||||
* @property int $node_id
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property string|null $status
|
||||
* @property bool $skip_scripts
|
||||
* @property int $owner_id
|
||||
* @property int $memory
|
||||
* @property int $swap
|
||||
* @property int $disk
|
||||
* @property int $io
|
||||
* @property int $cpu
|
||||
* @property string $threads
|
||||
* @property bool $oom_disabled
|
||||
* @property int $allocation_id
|
||||
* @property int $nest_id
|
||||
* @property int $egg_id
|
||||
* @property string $startup
|
||||
* @property string $image
|
||||
* @property int $allocation_limit
|
||||
* @property int $database_limit
|
||||
* @property int $backup_limit
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property \Pterodactyl\Models\User $user
|
||||
* @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subusers
|
||||
* @property \Pterodactyl\Models\Allocation $allocation
|
||||
* @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations
|
||||
* @property \Pterodactyl\Models\Node $node
|
||||
* @property \Pterodactyl\Models\Nest $nest
|
||||
* @property \Pterodactyl\Models\Egg $egg
|
||||
* @property \Pterodactyl\Models\EggVariable[]|\Illuminate\Database\Eloquent\Collection $variables
|
||||
* @property \Pterodactyl\Models\Schedule[]|\Illuminate\Database\Eloquent\Collection $schedule
|
||||
* @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases
|
||||
* @property \Pterodactyl\Models\Location $location
|
||||
* @property \Pterodactyl\Models\ServerTransfer $transfer
|
||||
* @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups
|
||||
* @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts
|
||||
* @property \Pterodactyl\Models\AuditLog[] $audits
|
||||
*/
|
||||
class Server extends Model
|
||||
{
|
||||
use BelongsToThrough;
|
||||
|
@ -84,6 +40,7 @@ class Server extends Model
|
|||
protected $attributes = [
|
||||
'status' => self::STATUS_INSTALLING,
|
||||
'oom_disabled' => true,
|
||||
'startup' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -124,7 +81,7 @@ class Server extends Model
|
|||
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
|
||||
'nest_id' => 'required|exists:nests,id',
|
||||
'egg_id' => 'required|exists:eggs,id',
|
||||
'startup' => 'required|string',
|
||||
'startup' => 'nullable|string',
|
||||
'skip_scripts' => 'sometimes|boolean',
|
||||
'image' => 'required|string|max:191',
|
||||
'database_limit' => 'present|nullable|integer|min:0',
|
||||
|
@ -239,6 +196,7 @@ class Server extends Model
|
|||
* Gets information for the service variables associated with this server.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
* @phpstan-return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\EggVariable>
|
||||
*/
|
||||
public function variables()
|
||||
{
|
||||
|
|
|
@ -58,7 +58,7 @@ class AccountCreated extends Notification implements ShouldQueue
|
|||
public function toMail($notifiable)
|
||||
{
|
||||
$message = (new MailMessage())
|
||||
->greeting('Hello ' . $this->user->name . '!')
|
||||
->greeting('Hello ' . $this->user->name_first . '!')
|
||||
->line('You are receiving this email because an account has been created for you on ' . config('app.name') . '.')
|
||||
->line('Username: ' . $this->user->username)
|
||||
->line('Email: ' . $this->user->email);
|
||||
|
|
|
@ -27,7 +27,7 @@ class MailTested extends Notification
|
|||
{
|
||||
return (new MailMessage())
|
||||
->subject('Pterodactyl Test Message')
|
||||
->greeting('Hello ' . $this->user->name . '!')
|
||||
->greeting('Hello ' . $this->user->name_first . '!')
|
||||
->line('This is a test of the Pterodactyl mail system. You\'re good to go!');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Pterodactyl\Notifications;
|
||||
|
||||
use Webmozart\Assert\Assert;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Pterodactyl\Events\Event;
|
||||
use Illuminate\Container\Container;
|
||||
|
@ -33,6 +34,8 @@ class ServerInstalled extends Notification implements ShouldQueue, ReceivesEvent
|
|||
*/
|
||||
public function handle(Event $event): void
|
||||
{
|
||||
Assert::propertyExists($event, 'server');
|
||||
|
||||
$event->server->loadMissing('user');
|
||||
|
||||
$this->server = $event->server;
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
namespace Pterodactyl\Providers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\Subuser;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Pterodactyl\Observers\UserObserver;
|
||||
|
@ -30,6 +32,15 @@ class AppServiceProvider extends ServiceProvider
|
|||
* @see https://laravel.com/docs/8.x/sanctum#overriding-default-models
|
||||
*/
|
||||
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
|
||||
|
||||
// If the APP_URL value is set with https:// make sure we force it here. Theoretically
|
||||
// this should just work with the proxy logic, but there are a lot of cases where it
|
||||
// doesn't, and it triggers a lot of support requests, so lets just head it off here.
|
||||
//
|
||||
// @see https://github.com/pterodactyl/panel/issues/3623
|
||||
if (Str::startsWith(config('app.url') ?? '', 'https://')) {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Pterodactyl\Providers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
@ -19,44 +20,87 @@ class RouteServiceProvider extends ServiceProvider
|
|||
protected $namespace = 'Pterodactyl\Http\Controllers';
|
||||
|
||||
/**
|
||||
* Define the routes for the application.
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
*/
|
||||
public function map()
|
||||
public function boot()
|
||||
{
|
||||
Route::middleware(['web', 'auth', 'csrf'])
|
||||
->namespace($this->namespace . '\Base')
|
||||
->group(base_path('routes/base.php'));
|
||||
$this->configureRateLimiting();
|
||||
|
||||
Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin')
|
||||
->namespace($this->namespace . '\Admin')
|
||||
->group(base_path('routes/admin.php'));
|
||||
$this->routes(function () {
|
||||
Route::middleware(['web', 'auth', 'csrf'])
|
||||
->namespace("$this->namespace\\Base")
|
||||
->group(base_path('routes/base.php'));
|
||||
|
||||
Route::middleware(['web', 'csrf'])->prefix('/auth')
|
||||
->namespace($this->namespace . '\Auth')
|
||||
->group(base_path('routes/auth.php'));
|
||||
Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin')
|
||||
->namespace("$this->namespace\\Admin")
|
||||
->group(base_path('routes/admin.php'));
|
||||
|
||||
Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance'])
|
||||
->prefix('/api/server/{server}')
|
||||
->namespace($this->namespace . '\Server')
|
||||
->group(base_path('routes/server.php'));
|
||||
Route::middleware(['web', 'csrf'])->prefix('/auth')
|
||||
->namespace("$this->namespace\\Auth")
|
||||
->group(base_path('routes/auth.php'));
|
||||
|
||||
Route::middleware([
|
||||
sprintf('throttle:%s,%s', config('http.rate_limit.application'), config('http.rate_limit.application_period')),
|
||||
'api',
|
||||
])->prefix('/api/application')
|
||||
->namespace($this->namespace . '\Api\Application')
|
||||
->group(base_path('routes/api-application.php'));
|
||||
Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance'])
|
||||
->prefix('/api/server/{server}')
|
||||
->namespace("$this->namespace\\Server")
|
||||
->group(base_path('routes/server.php'));
|
||||
|
||||
Route::middleware([
|
||||
//sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')),
|
||||
'client-api',
|
||||
])->prefix('/api/client')
|
||||
->namespace($this->namespace . '\Api\Client')
|
||||
->group(base_path('routes/api-client.php'));
|
||||
Route::middleware(['api', 'throttle:api.application'])
|
||||
->prefix('/api/application')
|
||||
->namespace("$this->namespace\\Api\\Application")
|
||||
->group(base_path('routes/api-application.php'));
|
||||
|
||||
Route::middleware(['daemon'])->prefix('/api/remote')
|
||||
->namespace($this->namespace . '\Api\Remote')
|
||||
->group(base_path('routes/api-remote.php'));
|
||||
Route::middleware(['client-api', 'throttle:api.client'])
|
||||
->prefix('/api/client')
|
||||
->namespace("$this->namespace\\Api\\Client")
|
||||
->group(base_path('routes/api-client.php'));
|
||||
|
||||
Route::middleware(['daemon'])->prefix('/api/remote')
|
||||
->namespace("$this->namespace\\Api\\Remote")
|
||||
->group(base_path('routes/api-remote.php'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the rate limiters for the application.
|
||||
*/
|
||||
protected function configureRateLimiting()
|
||||
{
|
||||
// Authentication rate limiting. For login and checkpoint endpoints we'll apply
|
||||
// a limit of 10 requests per minute, for the forgot password endpoint apply a
|
||||
// limit of two per minute for the requester so that there is less ability to
|
||||
// trigger email spam.
|
||||
RateLimiter::for('authentication', function (Request $request) {
|
||||
if ($request->route()->named('auth.post.forgot-password')) {
|
||||
return Limit::perMinute(2)->by($request->ip());
|
||||
}
|
||||
|
||||
return Limit::perMinute(10);
|
||||
});
|
||||
|
||||
// Configure the throttles for both the application and client APIs below.
|
||||
// This is configurable per-instance in "config/http.php". By default this
|
||||
// limiter will be tied to the specific request user, and falls back to the
|
||||
// request IP if there is no request user present for the key.
|
||||
//
|
||||
// This means that an authenticated API user cannot use IP switching to get
|
||||
// around the limits.
|
||||
RateLimiter::for('api.client', function (Request $request) {
|
||||
$key = optional($request->user())->uuid ?: $request->ip();
|
||||
|
||||
return Limit::perMinutes(
|
||||
config('http.rate_limit.client_period'),
|
||||
config('http.rate_limit.client')
|
||||
)->by($key);
|
||||
});
|
||||
|
||||
RateLimiter::for('api.application', function (Request $request) {
|
||||
$key = optional($request->user())->uuid ?: $request->ip();
|
||||
|
||||
return Limit::perMinutes(
|
||||
config('http.rate_limit.application_period'),
|
||||
config('http.rate_limit.application')
|
||||
)->by($key);
|
||||
});
|
||||
|
||||
RateLimiter::for('pull', function () {
|
||||
return Limit::perMinute(10);
|
||||
|
|
|
@ -21,7 +21,6 @@ class SettingsServiceProvider extends ServiceProvider
|
|||
protected $keys = [
|
||||
'app:name',
|
||||
'app:locale',
|
||||
'app:analytics',
|
||||
'recaptcha:enabled',
|
||||
'recaptcha:secret_key',
|
||||
'recaptcha:website_key',
|
||||
|
|
|
@ -20,10 +20,12 @@ class BackupRepository extends EloquentRepository
|
|||
/**
|
||||
* Determines if too many backups have been generated by the server.
|
||||
*
|
||||
* @return \Pterodactyl\Models\Backup[]|\Illuminate\Support\Collection
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @phpstan-return \Illuminate\Support\Collection<\Pterodactyl\Models\Backup>
|
||||
*/
|
||||
public function getBackupsGeneratedDuringTimespan(int $server, int $seconds = 600)
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()
|
||||
->withTrashed()
|
||||
->where('server_id', $server)
|
||||
|
|
|
@ -89,10 +89,8 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor
|
|||
|
||||
/**
|
||||
* Create a new database user on a given connection.
|
||||
*
|
||||
* @param $max_connections
|
||||
*/
|
||||
public function createUser(string $username, string $remote, string $password, $max_connections): bool
|
||||
public function createUser(string $username, string $remote, string $password, int $max_connections): bool
|
||||
{
|
||||
if (!$max_connections) {
|
||||
return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password));
|
||||
|
@ -132,8 +130,6 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor
|
|||
|
||||
/**
|
||||
* Drop a given user on a specific connection.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function dropUser(string $username, string $remote): bool
|
||||
{
|
||||
|
|
|
@ -29,6 +29,8 @@ class EggRepository extends EloquentRepository implements EggRepositoryInterface
|
|||
public function getWithVariables(int $id): Egg
|
||||
{
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->with('variables')->findOrFail($id, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
throw new RecordNotFoundException();
|
||||
|
@ -55,6 +57,8 @@ class EggRepository extends EloquentRepository implements EggRepositoryInterface
|
|||
Assert::true((is_digit($value) || is_string($value)), 'First argument passed to getWithCopyAttributes must be an integer or string, received %s.');
|
||||
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->with('scriptFrom', 'configFrom')->where($column, '=', $value)->firstOrFail($this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
throw new RecordNotFoundException();
|
||||
|
@ -69,6 +73,8 @@ class EggRepository extends EloquentRepository implements EggRepositoryInterface
|
|||
public function getWithExportAttributes(int $id): Egg
|
||||
{
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->with('scriptFrom', 'configFrom', 'variables')->findOrFail($id, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
throw new RecordNotFoundException();
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Pterodactyl\Repositories\Eloquent;
|
|||
|
||||
use Illuminate\Http\Request;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Models\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Pterodactyl\Repositories\Repository;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
@ -25,12 +26,8 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
|
|||
* Determines if the repository function should use filters off the request object
|
||||
* present when returning results. This allows repository methods to be called in API
|
||||
* context's such that we can pass through ?filter[name]=Dane&sort=desc for example.
|
||||
*
|
||||
* @param bool $usingFilters
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function usingRequestFilters($usingFilters = true)
|
||||
public function usingRequestFilters(bool $usingFilters = true): self
|
||||
{
|
||||
$this->useRequestFilters = $usingFilters;
|
||||
|
||||
|
@ -39,26 +36,22 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
|
|||
|
||||
/**
|
||||
* Returns the request instance.
|
||||
*
|
||||
* @return \Illuminate\Http\Request
|
||||
*/
|
||||
protected function request()
|
||||
protected function request(): Request
|
||||
{
|
||||
return $this->app->make(Request::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the response data based on the page para.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
protected function paginate(Builder $instance, int $default = 50)
|
||||
protected function paginate(Builder $instance, int $default = 50): LengthAwarePaginator
|
||||
{
|
||||
if (!$this->useRequestFilters) {
|
||||
return $instance->paginate($default);
|
||||
}
|
||||
|
||||
return $instance->paginate($this->request()->query('per_page', $default));
|
||||
return $instance->paginate((int) $this->request()->query('per_page', (string) $default));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,15 +84,20 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
|
|||
*/
|
||||
public function create(array $fields, bool $validate = true, bool $force = false)
|
||||
{
|
||||
/** @phpstan-var \Illuminate\Database\Eloquent\Model $instance */
|
||||
$instance = $this->getBuilder()->newModelInstance();
|
||||
($force) ? $instance->forceFill($fields) : $instance->fill($fields);
|
||||
|
||||
if (!$validate) {
|
||||
$saved = $instance->skipValidation()->save();
|
||||
} else {
|
||||
if (!$saved = $instance->save()) {
|
||||
throw new DataValidationException($instance->getValidator());
|
||||
if ($instance instanceof Model) {
|
||||
if (!$validate) {
|
||||
$saved = $instance->skipValidation()->save();
|
||||
} else {
|
||||
if (!$saved = $instance->save()) {
|
||||
throw new DataValidationException($instance->getValidator());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$saved = $instance->save();
|
||||
}
|
||||
|
||||
return ($this->withFresh) ? $instance->fresh() : $saved;
|
||||
|
@ -150,6 +148,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
|
|||
*/
|
||||
public function findCountWhere(array $fields): int
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->where($fields)->count($this->getColumns());
|
||||
}
|
||||
|
||||
|
@ -191,12 +190,16 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
|
|||
|
||||
($force) ? $instance->forceFill($fields) : $instance->fill($fields);
|
||||
|
||||
if (!$validate) {
|
||||
$saved = $instance->skipValidation()->save();
|
||||
} else {
|
||||
if (!$saved = $instance->save()) {
|
||||
throw new DataValidationException($instance->getValidator());
|
||||
if ($instance instanceof Model) {
|
||||
if (!$validate) {
|
||||
$saved = $instance->skipValidation()->save();
|
||||
} else {
|
||||
if (!$saved = $instance->save()) {
|
||||
throw new DataValidationException($instance->getValidator());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$saved = $instance->save();
|
||||
}
|
||||
|
||||
return ($this->withFresh) ? $instance->fresh() : $saved;
|
||||
|
@ -245,6 +248,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
|
|||
return $this->create(array_merge($where, $fields), $validate, $force);
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->update($instance->id, $fields, $validate, $force);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,13 +39,13 @@ class LocationRepository extends EloquentRepository implements LocationRepositor
|
|||
/**
|
||||
* Return all of the nodes and their respective count of servers for a location.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithNodes(int $id): Location
|
||||
{
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->with('nodes.servers')->findOrFail($id, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
throw new RecordNotFoundException();
|
||||
|
@ -55,13 +55,13 @@ class LocationRepository extends EloquentRepository implements LocationRepositor
|
|||
/**
|
||||
* Return a location and the count of nodes in that location.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithNodeCount(int $id): Location
|
||||
{
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->withCount('nodes')->findOrFail($id, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
throw new RecordNotFoundException();
|
||||
|
|
|
@ -31,13 +31,13 @@ class MountRepository extends EloquentRepository
|
|||
/**
|
||||
* Return all of the mounts and their respective relations.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithRelations(string $id): Mount
|
||||
{
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->with('eggs', 'nodes')->findOrFail($id, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
throw new RecordNotFoundException();
|
||||
|
|
|
@ -28,15 +28,14 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa
|
|||
/**
|
||||
* Return a nest or all nests with their associated eggs and variables.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithEggs(int $id = null)
|
||||
public function getWithEggs(int $id = null): Nest
|
||||
{
|
||||
$instance = $this->getBuilder()->with('eggs', 'eggs.variables');
|
||||
|
||||
if (!is_null($id)) {
|
||||
/** @var \Pterodactyl\Models\Nest|null $instance */
|
||||
$instance = $instance->find($id, $this->getColumns());
|
||||
if (!$instance) {
|
||||
throw new RecordNotFoundException();
|
||||
|
@ -45,45 +44,8 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa
|
|||
return $instance;
|
||||
}
|
||||
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $instance->get($this->getColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a nest or all nests and the count of eggs and servers for that nest.
|
||||
*
|
||||
* @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithCounts(int $id = null)
|
||||
{
|
||||
$instance = $this->getBuilder()->withCount(['eggs', 'servers']);
|
||||
|
||||
if (!is_null($id)) {
|
||||
$instance = $instance->find($id, $this->getColumns());
|
||||
if (!$instance) {
|
||||
throw new RecordNotFoundException();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
return $instance->get($this->getColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a nest along with its associated eggs and the servers relation on those eggs.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getWithEggServers(int $id): Nest
|
||||
{
|
||||
$instance = $this->getBuilder()->with('eggs.servers')->find($id, $this->getColumns());
|
||||
if (!$instance) {
|
||||
throw new RecordNotFoundException();
|
||||
}
|
||||
|
||||
/* @var Nest $instance */
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,10 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
|
|||
$this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
|
||||
)->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first();
|
||||
|
||||
return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) {
|
||||
return collect([
|
||||
'disk' => $stats->sum_disk,
|
||||
'memory' => $stats->sum_memory,
|
||||
])->mapWithKeys(function ($value, $key) use ($node) {
|
||||
$maxUsage = $node->{$key};
|
||||
if ($node->{$key . '_overallocate'} > 0) {
|
||||
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
|
||||
|
@ -85,6 +88,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
|
|||
|
||||
// This is quite ugly and can probably be improved down the road.
|
||||
// And by probably, I mean it should.
|
||||
// @phpstan-ignore-next-line
|
||||
if (is_null($node->servers_count) || $refresh) {
|
||||
$node->load('servers');
|
||||
$node->setRelation('servers_count', count($node->getRelation('servers')));
|
||||
|
@ -118,22 +122,28 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
|
|||
*/
|
||||
public function getNodesForServerCreation(): Collection
|
||||
{
|
||||
return $this->getBuilder()->with('allocations')->get()->map(function (Node $item) {
|
||||
/** @phpstan-var \Illuminate\Database\Eloquent\Collection<\Pterodactyl\Models\Node> $collection */
|
||||
$collection = $this->getBuilder()->with('allocations')->get();
|
||||
|
||||
return $collection->map(function (Node $item) {
|
||||
/** @phpstan-var \Illuminate\Support\Collection<array{id: string, ip: string, port: string|int}> $filtered */
|
||||
$filtered = $item->getRelation('allocations')->where('server_id', null)->map(function ($map) {
|
||||
return collect($map)->only(['id', 'ip', 'port']);
|
||||
});
|
||||
|
||||
$item->ports = $filtered->map(function ($map) {
|
||||
$ports = $filtered->map(function ($map) {
|
||||
return [
|
||||
'id' => $map['id'],
|
||||
'text' => sprintf('%s:%s', $map['ip'], $map['port']),
|
||||
];
|
||||
})->values();
|
||||
|
||||
$item->setAttribute('ports', $ports);
|
||||
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'text' => $item->name,
|
||||
'allocations' => $item->ports,
|
||||
'allocations' => $ports,
|
||||
];
|
||||
})->values();
|
||||
}
|
||||
|
@ -144,11 +154,22 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
|
|||
public function getNodeWithResourceUsage(int $node_id): Node
|
||||
{
|
||||
$instance = $this->getBuilder()
|
||||
->select(['nodes.id', 'nodes.fqdn', 'nodes.public_port_http', 'nodes.scheme', 'nodes.daemon_token', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate'])
|
||||
->select([
|
||||
'nodes.id',
|
||||
'nodes.fqdn',
|
||||
'nodes.public_port_http',
|
||||
'nodes.scheme',
|
||||
'nodes.daemon_token',
|
||||
'nodes.memory',
|
||||
'nodes.disk',
|
||||
'nodes.memory_overallocate',
|
||||
'nodes.disk_overallocate',
|
||||
])
|
||||
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
|
||||
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
|
||||
->where('nodes.id', $node_id);
|
||||
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
return $instance->first();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ class ScheduleRepository extends EloquentRepository implements ScheduleRepositor
|
|||
public function getScheduleWithTasks(int $schedule): Schedule
|
||||
{
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->with('tasks')->findOrFail($schedule, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
throw new RecordNotFoundException();
|
||||
|
|
|
@ -74,6 +74,8 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
|
|||
public function findWithVariables(int $id): Server
|
||||
{
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->with('egg.variables', 'variables')
|
||||
->where($this->getModel()->getKeyName(), '=', $id)
|
||||
->firstOrFail($this->getColumns());
|
||||
|
|
|
@ -27,6 +27,8 @@ class TaskRepository extends EloquentRepository implements TaskRepositoryInterfa
|
|||
public function getTaskForJobProcess(int $id): Task
|
||||
{
|
||||
try {
|
||||
/* @noinspection PhpIncompatibleReturnTypeInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getBuilder()->with('server.user', 'schedule')->findOrFail($id, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
throw new RecordNotFoundException();
|
||||
|
|
|
@ -118,7 +118,8 @@ abstract class Repository implements RepositoryInterface
|
|||
/**
|
||||
* Take the provided model and make it accessible to the rest of the repository.
|
||||
*
|
||||
* @param array $model
|
||||
* @param string[] $model
|
||||
* @phpstan-param class-string<\Illuminate\Database\Eloquent\Model> $model
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -128,6 +129,7 @@ abstract class Repository implements RepositoryInterface
|
|||
case 1:
|
||||
return $this->model = $this->app->make($model[0]);
|
||||
case 2:
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->model = call_user_func([$this->app->make($model[0]), $model[1]]);
|
||||
default:
|
||||
throw new InvalidArgumentException('Model must be a FQDN or an array with a count of two.');
|
||||
|
|
|
@ -22,7 +22,7 @@ class Username implements Rule
|
|||
*/
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
return preg_match(self::VALIDATION_REGEX, mb_strtolower($value));
|
||||
return preg_match(self::VALIDATION_REGEX, mb_strtolower($value)) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -64,6 +64,7 @@ class AssignmentService
|
|||
$parsed = Network::parse($underlying);
|
||||
} catch (Exception $exception) {
|
||||
/* @noinspection PhpUndefinedVariableInspection */
|
||||
// @phpstan-ignore-next-line
|
||||
throw new DisplayException("Could not parse provided allocation IP address ({$underlying}): {$exception->getMessage()}", $exception);
|
||||
}
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ class DeleteBackupService
|
|||
/** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */
|
||||
$adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3);
|
||||
|
||||
// @phpstan-ignore-next-line this is defined on the actual S3Client class, just not on the interface.
|
||||
$adapter->getClient()->deleteObject([
|
||||
'Bucket' => $adapter->getBucket(),
|
||||
'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid),
|
||||
|
|
|
@ -9,6 +9,7 @@ use Pterodactyl\Models\Backup;
|
|||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Extensions\Backups\BackupManager;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
||||
use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
|
||||
use Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException;
|
||||
|
@ -53,8 +54,6 @@ class InitiateBackupService
|
|||
|
||||
/**
|
||||
* InitiateBackupService constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService
|
||||
*/
|
||||
public function __construct(
|
||||
BackupRepository $repository,
|
||||
|
@ -140,7 +139,7 @@ class InitiateBackupService
|
|||
// Get the oldest backup the server has that is not "locked" (indicating a backup that should
|
||||
// never be automatically purged). If we find a backup we will delete it and then continue with
|
||||
// this process. If no backup is found that can be used an exception is thrown.
|
||||
/** @var \Pterodactyl\Models\Backup $oldest */
|
||||
/** @var \Pterodactyl\Models\Backup|null $oldest */
|
||||
$oldest = $successful->where('is_locked', false)->orderBy('created_at')->first();
|
||||
if (!$oldest) {
|
||||
throw new TooManyBackupsException($server->backup_limit);
|
||||
|
|
|
@ -152,6 +152,7 @@ class DatabaseManagementService
|
|||
});
|
||||
} catch (Exception $exception) {
|
||||
try {
|
||||
// @phpstan-ignore-next-line doesn't understand the pass-by-reference above
|
||||
if ($database instanceof Database) {
|
||||
$this->repository->dropDatabase($database->database);
|
||||
$this->repository->dropUser($database->username, $database->remote);
|
||||
|
|
|
@ -49,8 +49,6 @@ class DatabasePasswordService
|
|||
/**
|
||||
* Updates a password for a given database.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Database|int $database
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function handle(Database $database): string
|
||||
|
|
|
@ -205,6 +205,7 @@ class EggConfigurationService
|
|||
// Replace anything starting with "server." with the value out of the server configuration
|
||||
// array that used to be created for the old daemon.
|
||||
if (Str::startsWith($key, 'server.')) {
|
||||
// @phpstan-ignore-next-line
|
||||
$plucked = Arr::get($structure, preg_replace('/^server\./', '', $key), '');
|
||||
|
||||
$value = str_replace("{{{$key}}}", $plucked, $value);
|
||||
|
@ -215,6 +216,7 @@ class EggConfigurationService
|
|||
// variable from the server configuration.
|
||||
$plucked = Arr::get(
|
||||
$structure,
|
||||
// @phpstan-ignore-next-line
|
||||
preg_replace('/^env\./', 'build.env.', $key),
|
||||
''
|
||||
);
|
||||
|
|
|
@ -24,13 +24,11 @@ class InstallScriptService
|
|||
/**
|
||||
* Modify the install script for a given Egg.
|
||||
*
|
||||
* @param int|\Pterodactyl\Models\Egg $egg
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException
|
||||
*/
|
||||
public function handle(Egg $egg, array $data)
|
||||
public function handle(Egg $egg, array $data): void
|
||||
{
|
||||
if (!is_null(array_get($data, 'copy_script_from'))) {
|
||||
if (!$this->repository->isCopyableScript(array_get($data, 'copy_script_from'), $egg->nest_id)) {
|
||||
|
|
|
@ -27,11 +27,11 @@ class EggExporterService
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function handle(int $egg): string
|
||||
public function handle(int $egg): array
|
||||
{
|
||||
$egg = $this->repository->getWithExportAttributes($egg);
|
||||
|
||||
$struct = [
|
||||
return [
|
||||
'_comment' => 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO',
|
||||
'meta' => [
|
||||
'version' => 'PTDL_v1',
|
||||
|
@ -50,7 +50,6 @@ class EggExporterService
|
|||
'config' => [
|
||||
'files' => $egg->inherit_config_files,
|
||||
'startup' => $egg->inherit_config_startup,
|
||||
'logs' => $egg->inherit_config_logs,
|
||||
'stop' => $egg->inherit_config_stop,
|
||||
],
|
||||
'scripts' => [
|
||||
|
@ -66,7 +65,5 @@ class EggExporterService
|
|||
->toArray();
|
||||
}),
|
||||
];
|
||||
|
||||
return json_encode($struct, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ use Symfony\Component\Yaml\Exception\ParseException;
|
|||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException;
|
||||
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
|
||||
use Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException;
|
||||
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
|
||||
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
|
||||
|
||||
class EggImporterService
|
||||
|
@ -58,7 +58,7 @@ class EggImporterService
|
|||
/**
|
||||
* Take an uploaded JSON file and parse it into a new egg.
|
||||
*
|
||||
* @deprecated Use `handleFile` or `handleContent` instead.
|
||||
* @deprecated use `handleFile` or `handleContent` instead
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
|
||||
|
@ -151,7 +151,6 @@ class EggImporterService
|
|||
'update_url' => Arr::get($parsed, 'meta.update_url'),
|
||||
'config_files' => Arr::get($parsed, 'config.files'),
|
||||
'config_startup' => Arr::get($parsed, 'config.startup'),
|
||||
'config_logs' => Arr::get($parsed, 'config.logs'),
|
||||
'config_stop' => Arr::get($parsed, 'config.stop'),
|
||||
'startup' => Arr::get($parsed, 'startup'),
|
||||
'script_install' => Arr::get($parsed, 'scripts.installation.script'),
|
||||
|
|
|
@ -74,7 +74,6 @@ class EggUpdateImporterService
|
|||
'docker_images' => object_get($parsed, 'images') ?? [object_get($parsed, 'image')],
|
||||
'config_files' => object_get($parsed, 'config.files'),
|
||||
'config_startup' => object_get($parsed, 'config.startup'),
|
||||
'config_logs' => object_get($parsed, 'config.logs'),
|
||||
'config_stop' => object_get($parsed, 'config.stop'),
|
||||
'startup' => object_get($parsed, 'startup'),
|
||||
'script_install' => object_get($parsed, 'scripts.installation.script'),
|
||||
|
|
|
@ -3,31 +3,21 @@
|
|||
namespace Pterodactyl\Services\Eggs\Variables;
|
||||
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Illuminate\Contracts\Validation\Factory;
|
||||
use Illuminate\Contracts\Validation\Factory as Validator;
|
||||
use Pterodactyl\Traits\Services\ValidatesValidationRules;
|
||||
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException;
|
||||
|
||||
class VariableCreationService
|
||||
{
|
||||
use ValidatesValidationRules;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Validation\Factory
|
||||
*/
|
||||
private $validator;
|
||||
private Validator $validator;
|
||||
|
||||
/**
|
||||
* VariableCreationService constructor.
|
||||
*/
|
||||
public function __construct(EggVariableRepositoryInterface $repository, Factory $validator)
|
||||
public function __construct(Validator $validator)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
|
@ -35,7 +25,7 @@ class VariableCreationService
|
|||
* Return the validation factory instance to be used by rule validation
|
||||
* checking in the trait.
|
||||
*/
|
||||
protected function getValidator(): Factory
|
||||
protected function getValidator(): Validator
|
||||
{
|
||||
return $this->validator;
|
||||
}
|
||||
|
@ -43,7 +33,6 @@ class VariableCreationService
|
|||
/**
|
||||
* Create a new variable for a given Egg.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
||||
*/
|
||||
|
@ -59,15 +48,18 @@ class VariableCreationService
|
|||
|
||||
$options = array_get($data, 'options') ?? [];
|
||||
|
||||
return $this->repository->create([
|
||||
/** @var \Pterodactyl\Models\EggVariable $model */
|
||||
$model = EggVariable::query()->create([
|
||||
'egg_id' => $egg,
|
||||
'name' => $data['name'] ?? '',
|
||||
'description' => $data['description'] ?? '',
|
||||
'env_variable' => $data['env_variable'] ?? '',
|
||||
'default_value' => $data['default_value'] ?? '',
|
||||
'user_viewable' => in_array('user_viewable', $options),
|
||||
'user_editable' => in_array('user_editable', $options),
|
||||
'user_viewable' => $data['user_viewable'],
|
||||
'user_editable' => $data['user_editable'],
|
||||
'rules' => $data['rules'] ?? '',
|
||||
]);
|
||||
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,22 +3,17 @@
|
|||
namespace Pterodactyl\Services\Eggs\Variables;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Illuminate\Contracts\Validation\Factory;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
use Pterodactyl\Traits\Services\ValidatesValidationRules;
|
||||
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException;
|
||||
|
||||
class VariableUpdateService
|
||||
{
|
||||
use ValidatesValidationRules;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Validation\Factory
|
||||
*/
|
||||
|
@ -27,9 +22,8 @@ class VariableUpdateService
|
|||
/**
|
||||
* VariableUpdateService constructor.
|
||||
*/
|
||||
public function __construct(EggVariableRepositoryInterface $repository, Factory $validator)
|
||||
public function __construct(Factory $validator)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
|
@ -45,27 +39,22 @@ class VariableUpdateService
|
|||
/**
|
||||
* Update a specific egg variable.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
||||
*/
|
||||
public function handle(EggVariable $variable, array $data)
|
||||
public function handle(Egg $egg, array $data)
|
||||
{
|
||||
if (!is_null(array_get($data, 'env_variable'))) {
|
||||
if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) {
|
||||
throw new ReservedVariableNameException(trans('exceptions.service.variables.reserved_name', ['name' => array_get($data, 'env_variable')]));
|
||||
}
|
||||
|
||||
$search = $this->repository->setColumns('id')->findCountWhere([
|
||||
['env_variable', '=', $data['env_variable']],
|
||||
['egg_id', '=', $variable->egg_id],
|
||||
['id', '!=', $variable->id],
|
||||
]);
|
||||
$count = $egg->variables()
|
||||
->where('egg_variables.env_variable', $data['env_variable'])
|
||||
->where('egg_variables.id', '!=', $data['id'])
|
||||
->count();
|
||||
|
||||
if ($search > 0) {
|
||||
if ($count > 0) {
|
||||
throw new DisplayException(trans('exceptions.service.variables.env_not_unique', ['name' => array_get($data, 'env_variable')]));
|
||||
}
|
||||
}
|
||||
|
@ -80,13 +69,13 @@ class VariableUpdateService
|
|||
|
||||
$options = array_get($data, 'options') ?? [];
|
||||
|
||||
return $this->repository->withoutFreshModel()->update($variable->id, [
|
||||
$egg->variables()->where('egg_variables.id', $data['id'])->update([
|
||||
'name' => $data['name'] ?? '',
|
||||
'description' => $data['description'] ?? '',
|
||||
'env_variable' => $data['env_variable'] ?? '',
|
||||
'default_value' => $data['default_value'] ?? '',
|
||||
'user_viewable' => in_array('user_viewable', $options),
|
||||
'user_editable' => in_array('user_editable', $options),
|
||||
'user_viewable' => $data['user_viewable'],
|
||||
'user_editable' => $data['user_editable'],
|
||||
'rules' => $data['rules'] ?? '',
|
||||
]);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue