diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..d8a888ed2 --- /dev/null +++ b/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["es2015"], + "compact": true, + "minified": true, + "only": "public/themes/pterodactyl/js/frontend/files/src/*.js", + "sourceMaps": "inline", + "comments": false +} diff --git a/.dev/vagrant/.env.vagrant b/.dev/vagrant/.env.vagrant new file mode 100644 index 000000000..2427ec04e --- /dev/null +++ b/.dev/vagrant/.env.vagrant @@ -0,0 +1,39 @@ +APP_ENV=develop +APP_DEBUG=true +APP_KEY=SomeRandomString3232RandomString +APP_THEME=pterodactyl +APP_TIMEZONE=UTC +APP_CLEAR_TASKLOG=720 +APP_DELETE_MINUTES=10 +APP_URL=http://192.168.50.2/ + +DB_HOST=localhost +DB_PORT=3306 +DB_DATABASE=panel +DB_USERNAME=pterodactyl +DB_PASSWORD=pterodactyl + +CACHE_DRIVER=memcached +MEMCACHED_HOST=127.0.0.1 +SESSION_DRIVER=database + +MAIL_DRIVER=smtp +MAIL_HOST=127.0.0.1 +MAIL_PORT=1025 +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_ENCRYPTION= +MAIL_FROM=support@pterodactyl.io + +API_PREFIX=api +API_VERSION=v1 +API_DEBUG=true + +QUEUE_DRIVER=database +QUEUE_HIGH=high +QUEUE_STANDARD=standard +QUEUE_LOW=low + +SQS_KEY=aws-public +SQS_SECRET=aws-secret +SQS_QUEUE_PREFIX=aws-queue-prefix diff --git a/.dev/vagrant/mailhog.service b/.dev/vagrant/mailhog.service new file mode 100644 index 000000000..01334183d --- /dev/null +++ b/.dev/vagrant/mailhog.service @@ -0,0 +1,13 @@ +[Unit] +Description=Mailhog + +[Service] +# On some systems the user and group might be different. +# Some systems use `apache` as the user and group. +User=www-data +Group=www-data +Restart=on-failure +ExecStart=/usr/bin/mailhog + +[Install] +WantedBy=multi-user.target diff --git a/.dev/vagrant/mariadb.cnf b/.dev/vagrant/mariadb.cnf new file mode 100644 index 000000000..48b31ed8b --- /dev/null +++ b/.dev/vagrant/mariadb.cnf @@ -0,0 +1,189 @@ +# MariaDB database server configuration file. +# +# You can copy this file to one of: +# - "/etc/mysql/my.cnf" to set global options, +# - "~/.my.cnf" to set user-specific options. +# +# One can use all long options that the program supports. +# Run program with --help to get a list of available options and with +# --print-defaults to see which it would actually understand and use. +# +# For explanations see +# http://dev.mysql.com/doc/mysql/en/server-system-variables.html + +# This will be passed to all mysql clients +# It has been reported that passwords should be enclosed with ticks/quotes +# escpecially if they contain "#" chars... +# Remember to edit /etc/mysql/debian.cnf when changing the socket location. +[client] +port = 3306 +socket = /var/run/mysqld/mysqld.sock + +# Here is entries for some specific programs +# The following values assume you have at least 32M ram + +# This was formally known as [safe_mysqld]. Both versions are currently parsed. +[mysqld_safe] +socket = /var/run/mysqld/mysqld.sock +nice = 0 + +[mysqld] +# +# * Basic Settings +# +user = mysql +pid-file = /var/run/mysqld/mysqld.pid +socket = /var/run/mysqld/mysqld.sock +port = 3306 +basedir = /usr +datadir = /var/lib/mysql +tmpdir = /tmp +lc_messages_dir = /usr/share/mysql +lc_messages = en_US +skip-external-locking +# +# Instead of skip-networking the default is now to listen only on +# localhost which is more compatible and is not less secure. +bind-address = 0.0.0.0 +# +# * Fine Tuning +# +max_connections = 100 +connect_timeout = 5 +wait_timeout = 600 +max_allowed_packet = 16M +thread_cache_size = 128 +sort_buffer_size = 4M +bulk_insert_buffer_size = 16M +tmp_table_size = 32M +max_heap_table_size = 32M +# +# * MyISAM +# +# This replaces the startup script and checks MyISAM tables if needed +# the first time they are touched. On error, make copy and try a repair. +myisam_recover_options = BACKUP +key_buffer_size = 128M +#open-files-limit = 2000 +table_open_cache = 400 +myisam_sort_buffer_size = 512M +concurrent_insert = 2 +read_buffer_size = 2M +read_rnd_buffer_size = 1M +# +# * Query Cache Configuration +# +# Cache only tiny result sets, so we can fit more in the query cache. +query_cache_limit = 128K +query_cache_size = 64M +# for more write intensive setups, set to DEMAND or OFF +#query_cache_type = DEMAND +# +# * Logging and Replication +# +# Both location gets rotated by the cronjob. +# Be aware that this log type is a performance killer. +# As of 5.1 you can enable the log at runtime! +#general_log_file = /var/log/mysql/mysql.log +#general_log = 1 +# +# Error logging goes to syslog due to /etc/mysql/conf.d/mysqld_safe_syslog.cnf. +# +# we do want to know about network errors and such +log_warnings = 2 +# +# Enable the slow query log to see queries with especially long duration +#slow_query_log[={0|1}] +slow_query_log_file = /var/log/mysql/mariadb-slow.log +long_query_time = 10 +#log_slow_rate_limit = 1000 +log_slow_verbosity = query_plan + +#log-queries-not-using-indexes +#log_slow_admin_statements +# +# The following can be used as easy to replay backup logs or for replication. +# note: if you are setting up a replication slave, see README.Debian about +# other settings you may need to change. +#server-id = 1 +#report_host = master1 +#auto_increment_increment = 2 +#auto_increment_offset = 1 +log_bin = /var/log/mysql/mariadb-bin +log_bin_index = /var/log/mysql/mariadb-bin.index +# not fab for performance, but safer +#sync_binlog = 1 +expire_logs_days = 10 +max_binlog_size = 100M +# slaves +#relay_log = /var/log/mysql/relay-bin +#relay_log_index = /var/log/mysql/relay-bin.index +#relay_log_info_file = /var/log/mysql/relay-bin.info +#log_slave_updates +#read_only +# +# If applications support it, this stricter sql_mode prevents some +# mistakes like inserting invalid dates etc. +#sql_mode = NO_ENGINE_SUBSTITUTION,TRADITIONAL +# +# * InnoDB +# +# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. +# Read the manual for more InnoDB related options. There are many! +default_storage_engine = InnoDB +# you can't just change log file size, requires special procedure +#innodb_log_file_size = 50M +innodb_buffer_pool_size = 256M +innodb_log_buffer_size = 8M +innodb_file_per_table = 1 +innodb_open_files = 400 +innodb_io_capacity = 400 +innodb_flush_method = O_DIRECT +# +# * Security Features +# +# Read the manual, too, if you want chroot! +# chroot = /var/lib/mysql/ +# +# For generating SSL certificates I recommend the OpenSSL GUI "tinyca". +# +# ssl-ca=/etc/mysql/cacert.pem +# ssl-cert=/etc/mysql/server-cert.pem +# ssl-key=/etc/mysql/server-key.pem + +# +# * Galera-related settings +# +[galera] +# Mandatory settings +#wsrep_on=ON +#wsrep_provider= +#wsrep_cluster_address= +#binlog_format=row +#default_storage_engine=InnoDB +#innodb_autoinc_lock_mode=2 +# +# Allow server to accept connections on all interfaces. +# +#bind-address=0.0.0.0 +# +# Optional setting +#wsrep_slave_threads=1 +#innodb_flush_log_at_trx_commit=0 + +[mysqldump] +quick +quote-names +max_allowed_packet = 16M + +[mysql] +#no-auto-rehash # faster start of mysql but no tab completion + +[isamchk] +key_buffer = 16M + +# +# * IMPORTANT: Additional settings that can override those from this file! +# The files must end with '.cnf', otherwise they'll be ignored. +# +!includedir /etc/mysql/conf.d/ diff --git a/.dev/vagrant/motd.txt b/.dev/vagrant/motd.txt new file mode 100644 index 000000000..823172e45 --- /dev/null +++ b/.dev/vagrant/motd.txt @@ -0,0 +1,17 @@ +##################################################### + Pterodactyl Panel Vagrant VM + +Install: /var/www/html/pterodactyl +Ports: + Panel: 80 (50080 on host) + MailHog: 8025 (58025 on host) + MySQL: 3306 (53306 on host) + +Default panel users: + user: admin passwd: Ptero123 (admin user) + user: user passwd: Ptero123 (standard user) + +MySQL is accessible using root/pterodactyl or pterodactyl/pterodactyl + +Services for pteroq and mailhog are running +##################################################### diff --git a/.dev/vagrant/provision.sh b/.dev/vagrant/provision.sh new file mode 100644 index 000000000..f5a35a0f2 --- /dev/null +++ b/.dev/vagrant/provision.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +echo "Provisioning development environment for Pterodactyl Panel." +cp /var/www/html/pterodactyl/.dev/vagrant/motd.txt /etc/motd +chmod -x /etc/update-motd.d/10-help-text /etc/update-motd.d/51-cloudguest + +apt-get install -y software-properties-common > /dev/null + +echo "Add the ondrej/php ppa repository" +add-apt-repository -y ppa:ondrej/php > /dev/null +echo "Add the mariadb repository" +curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash > /dev/null + +apt-get update > /dev/null + +echo "Install the dependencies" +export DEBIAN_FRONTEND=noninteractive +# set the mariadb root password because mariadb asks for it +debconf-set-selections <<< 'mariadb-server-5.5 mysql-server/root_password password pterodactyl' +debconf-set-selections <<< 'mariadb-server-5.5 mysql-server/root_password_again password pterodactyl' +# actually install +apt-get install -y php7.1 php7.1-cli php7.1-gd php7.1-mysql php7.1-pdo php7.1-mbstring php7.1-tokenizer php7.1-bcmath php7.1-xml php7.1-fpm php7.1-memcached php7.1-curl php7.1-zip php-xdebug mariadb-server nginx curl tar unzip git memcached > /dev/null + +echo "Install composer" +curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +echo "Install and run mailhog" +curl -sL -o /usr/bin/mailhog https://github.com/mailhog/MailHog/releases/download/v1.0.0/MailHog_linux_amd64 +chmod +x /usr/bin/mailhog +cp /var/www/html/pterodactyl/.dev/vagrant/mailhog.service /etc/systemd/system/ +systemctl enable mailhog.service +systemctl start mailhog + +echo "Configure xDebug" +cp /var/www/html/pterodactyl/.dev/vagrant/xdebug.ini /etc/php/7.1/mods-available/ +systemctl restart php7.1-fpm + +echo "Configure nginx" +cp /var/www/html/pterodactyl/.dev/vagrant/pterodactyl.conf /etc/nginx/sites-available/ +rm /etc/nginx/sites-available/default +ln -s /etc/nginx/sites-available/pterodactyl.conf /etc/nginx/sites-enabled/pterodactyl.conf +systemctl restart nginx + +echo "Setup database" +# Replace default config with custom one to bind mysql to 0.0.0.0 to make it accessible from the host +cp /var/www/html/pterodactyl/.dev/vagrant/mariadb.cnf /etc/mysql/my.cnf +systemctl restart mariadb +mysql -u root -ppterodactyl << SQL +CREATE DATABASE panel; +GRANT ALL PRIVILEGES ON panel.* TO 'pterodactyl'@'%' IDENTIFIED BY 'pterodactyl' WITH GRANT OPTION; +GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'pterodactyl' WITH GRANT OPTION; +FLUSH PRIVILEGES; +SQL + +echo "Setup pterodactyl queue worker service" +cp /var/www/html/pterodactyl/.dev/vagrant/pteroq.service /etc/systemd/system/ +systemctl enable pteroq.service + + +echo "Setup panel with base settings" +cp /var/www/html/pterodactyl/.dev/vagrant/.env.vagrant /var/www/html/pterodactyl/.env +cd /var/www/html/pterodactyl +chmod -R 755 storage/* bootstrap/cache +composer install --no-progress +php artisan key:generate --force +php artisan migrate +php artisan db:seed +php artisan pterodactyl:user --firstname Test --lastname Admin --username admin --email testadmin@pterodactyl.io --password Ptero123 --admin 1 +php artisan pterodactyl:user --firstname Test --lastname User --username user --email testuser@pterodactyl.io --password Ptero123 --admin 0 + +echo "Add queue cronjob and start queue worker" +(crontab -l 2>/dev/null; echo "* * * * * php /var/www/html/pterodactyl/artisan schedule:run >> /dev/null 2>&1") | crontab - +systemctl start pteroq + +echo " ----------------" +echo "Provisioning is completed." +echo "The panel should be available at http://localhost:50080/" +echo "You may use the default admin user to login: admin/Ptero123" +echo "A normal user has also been created: user/Ptero123" +echo "MailHog is available at http://localhost:58025/" +echo "Connect to the database using root/pterodactyl or pterodactyl/pterodactyl on localhost:53306" +echo "If you want to access the panel using http://pterodactyl.app you can use the vagrant-dns plugin" +echo "Install it with 'vagrant plugin install vagrant-dns', then run 'vagrant dns --install' once" +echo "On first use you'll have to manually start vagrant-dns with 'vagrant dns --start'" diff --git a/.dev/vagrant/pterodactyl.conf b/.dev/vagrant/pterodactyl.conf new file mode 100644 index 000000000..d83849a97 --- /dev/null +++ b/.dev/vagrant/pterodactyl.conf @@ -0,0 +1,51 @@ +# If using Ubuntu this file should be placed in: +# /etc/nginx/sites-available/ +# +# If using CentOS this file should be placed in: +# /etc/nginx/conf.d/ +# +server { + listen 80; + server_name 0.0.0.0; + + root /var/www/html/pterodactyl/public; + index index.html index.htm index.php; + charset utf-8; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log /var/log/nginx/pterodactyl.app-error.log error; + + # allow larger file uploads and longer script runtimes + client_max_body_size 100m; + client_body_timeout 120s; + + sendfile off; + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + # the fastcgi_pass path needs to be changed accordingly when using CentOS + fastcgi_pass unix:/var/run/php/php7.1-fpm.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M"; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTP_PROXY ""; + fastcgi_intercept_errors off; + fastcgi_buffer_size 16k; + fastcgi_buffers 4 16k; + fastcgi_connect_timeout 300; + fastcgi_send_timeout 300; + fastcgi_read_timeout 300; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/.dev/vagrant/pteroq.service b/.dev/vagrant/pteroq.service new file mode 100644 index 000000000..7828ee91b --- /dev/null +++ b/.dev/vagrant/pteroq.service @@ -0,0 +1,20 @@ +# Pterodactyl Queue Worker File +# ---------------------------------- +# File should be placed in: +# /etc/systemd/system +# +# nano /etc/systemd/system/pteroq.service + +[Unit] +Description=Pterodactyl Queue Worker + +[Service] +# On some systems the user and group might be different. +# Some systems use `apache` as the user and group. +User=www-data +Group=www-data +Restart=on-failure +ExecStart=/usr/bin/php /var/www/html/pterodactyl/artisan queue:work database --queue=high,standard,low --sleep=3 --tries=3 + +[Install] +WantedBy=multi-user.target diff --git a/.dev/vagrant/xdebug.ini b/.dev/vagrant/xdebug.ini new file mode 100644 index 000000000..1725b8e84 --- /dev/null +++ b/.dev/vagrant/xdebug.ini @@ -0,0 +1,10 @@ +zend_extension=xdebug.so + +xdebug.remote_enable=1 +xdebug.remote_connect_back=1 +xdebug.remote_port=9000 +xdebug.scream=0 +xdebug.show_local_vars=1 +xdebug.idekey=PHPSTORM + +xdebug.remote_log=/tmp/xdebug.log \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..bc49d523e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example index ba98b1c75..fa7e20965 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,11 @@ APP_ENV=production APP_DEBUG=false APP_KEY=SomeRandomString3232RandomString -APP_THEME=default +APP_THEME=pterodactyl APP_TIMEZONE=UTC APP_CLEAR_TASKLOG=720 APP_DELETE_MINUTES=10 -CONSOLE_PUSH_FREQ=250 -CONSOLE_PUSH_COUNT=10 +APP_URL=http://yoursite.com/ DB_HOST=localhost DB_PORT=3306 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 1674b0b9e..346d32290 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,61 +1,6 @@ - - -## Product -Please check the corresponding boxes below for which products this is about. +Please describe your issue in full below. Include what products are affected, as well as what version(s) you are running. Please also include information about your system, such as `uname -a` and `php -v` and `docker info` if applicable. -- [ ] Panel -- [ ] Daemon -- [ ] Dockerfile(s) [Please list if so: __ ] +If you're just making a suggestion, be descriptive, and link to any issues that might be releated as well. -## Type -- [ ] Bug or Issue -- [ ] Feature Request -- [ ] Enhancement -- [ ] Other - - - - -## What Happens - - -## How to Reproduce - - -1. Step 1 -2. Step 2 -3. etc. - -## Error Logs - - - - -``` -error logs -``` - -## System Information -#### Output of `uname -a`: - -``` -paste here -``` - -#### Output of `php -v` (if Panel): - -``` -paste here -``` - -#### Output of `node -v` (if Daemon): - -``` -paste here -``` - -#### Output of `docker info` and `docker -v` (if Daemon or Dockerfiles): - -``` -paste here -``` +You can delete from this line up. +--------------------- diff --git a/.gitignore b/.gitignore index 9dfd2bc12..9d8eca6a5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,24 @@ *.DS_Store* .env .vagrant/* +.vscode/* +storage/framework/* +/.idea composer.lock - -Homestead.yaml -Vagrantfile -Vagrantfile - node_modules -.babelrc \ No newline at end of file + +_ide_helper_models.php +_ide_helper.php + +sami.phar +/.sami + +# For local development with docker +# Remove if we ever put the Dockerfile in the repo +.dockerignore +Dockerfile +docker-compose.yml +# for image related files +misc +.phpstorm.meta.php diff --git a/.php_cs b/.php_cs new file mode 100644 index 000000000..791899b72 --- /dev/null +++ b/.php_cs @@ -0,0 +1,7 @@ +files() + ->name('*.php') + ->in($dir = __DIR__ . '/app'); + +return new Sami($iterator, array( + 'title' => 'Pterodactyl', + 'build_dir' => __DIR__ . '/.sami/build', + 'cache_dir' => __DIR__ . '/.sami/cache', + 'default_opened_level' => 2, +)); diff --git a/.styleci.yml b/.styleci.yml new file mode 100644 index 000000000..7595f5546 --- /dev/null +++ b/.styleci.yml @@ -0,0 +1,6 @@ +preset: laravel +risky: false +disabled: + - concat_without_spaces +enabled: + - concat_with_spaces diff --git a/CHANGELOG.md b/CHANGELOG.md index d612005e8..b3dacb3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,408 @@ 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. +## v0.6.4 (Courageous Carniadactylus) +### Fixed +* Fixed the console rendering on page load, I guess people don't like watching it load line-by-line for 10 minutes. Who would have guessed... +* Re-added support for up/down arrows loading previous commands in the console window. + +### Changed +* Panel API for Daemon now responds with a `HTTP/401 Unauthorized` error when unable to locate a node with a given authentication token, rather than a `HTTP/404 Not Found` response. +* Added better colors and styling for the terminal that can be adjusted per-theme. +* Session timeout adjusted to be 7 days by default. + +## v0.6.3 (Courageous Carniadactylus) +### Fixed +* **[Security]** — Addresses an oversight in how the terminal rendered information sent from the server feed which allowed a malicious user to execute arbitrary commands on the game-server process itself by using a specifically crafted in-game command. + +### Changed +* Removed `jquery.terminal` and replaced it with an in-house developed terminal with less potential for security issues. + +## v0.6.2 (Courageous Carniadactylus) +### Fixed +* Fixes a few typos throughout the panel, there are more don't worry. +* Fixes bug when disabling 2FA due to a misnamed route. +* API now returns a 404 error when deleting a user that doesn't exist, rather than saying it was successful. +* Service variables that allow empty input now allow you to empty out the assigned value and set it back to blank. +* Fixes a bug where changing the default allocation for a server would not actually apply that allocation as the default on the daemon. +* Newly created service variables are now backfilled and assigned to existing servers properly. + +### Added +* Added a `Vagrantfile` to the repository to help speed up development and testing for those who don't want to do a full dedicated install. +* Added a confirmation dialog to the logout button for admins to prevent misguided clickers from accidentally logging out when they wanted to switch to Admin or Server views. + +### Changed +* Blocked out the `Reinstall` button for servers that have failed installation to avoid confusion and bugs causing the daemon to break. +* Updated dependencies, listed below. +``` +aws/aws-sdk-php (3.26.5 => 3.29.7) +laravel/framework (v5.4.21 => v5.4.27) +barryvdh/laravel-debugbar (v2.3.2 => v2.4.0) +fideloper/proxy (3.3.0 => 3.3.3) +igaster/laravel-theme (v1.14 => v1.16) +laravel/tinker (v1.0.0 => v1.0.1) +spatie/laravel-fractal (4.0.0 => 4.0.1) +``` + +## v0.6.1 (Courageous Carniadactylus) +### Fixed +* Fixes a bug preventing the use of services that have no variables attached to them. +* Fixes 'Remember Me' checkbox being ignored when using 2FA on an account. +* API now returns a useful error displaying what went wrong rather than an obscure 'An Error was Encountered' message when API issues arise. +* Fixes bug preventing the creation of new files in the file manager due to a missing JS dependency on page load. +* Prevent using a service option tag that contains special characters that are not valid. Now only allows alpha-numeric, no spaces or underscores. +* Fix unhandled excpetion due to missing `Log` class when using the API and causing an error. + +### Changed +* Renamed session cookies from `laravel_session` to `pterodactyl_session`. +* Sessions are now encrypted before being stored as an additional layer of security. +* It is now possible to clear out a server description and have it be blank, rather than throwing an error about the field being required. + +## v0.6.0 (Courageous Carniadactylus) +### Fixed +* Bug causing error logs to be spammed if someone timed out on an ajax based page. +* Fixes edge case where specific server names could cause daemon errors due to an invalid SFTP username being created by the panel. +* Fixes sessions being removed on browser close, and set sessions to idle for up to 3 hours before being marked as expired. +* Emails sending with 'Pterodactyl Panel' as the from name. Now configurable by using `php artisan pterodactyl:mail` to update. +* Fixes potential bug with invalid CIDR notation (ex: `192.168.1.1/z`) when adding allocations that could cause over 4 million records to be created at once. +* Fixes bug where daemon was unable to register that certain games had fully booted and were ready to play on. +* Fixes bug causing MySQL user accounts to be corrupted when resetting a password via the panel. +* Fixes remote timing attack vulnerability due to hmac comparsion in API middleware. +* `[rc.1]` — Server deletion is fixed, caused by removed download table. +* `[rc.1]` — Server status indication on front-end no longer shows `Error` when server is marked as installing or suspended. +* `[rc.1]` — Fixes issues with SteamCMD not registering and installing games properly. + +### Changed +* Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields. +* User model now defines mass assignment fields using `$fillable` rather than `$guarded`. +* 2FA checkpoint on login is now its own page, and not an AJAX based call. Improves security on that front. +* Updated Server model code to be more efficient, as well as make life easier for backend changes and work. +* Reduced the number of database queries being executed when viewing a specific server. This is done by caching the query for up to 15 minutes in memcached. +* User creation emails include more information and are sent by the event listener rather than the repository. +* Account password reset emails now auto-fill the email when clicking the link. +* New theme applied to Admin CP. Many graphical changes were made, some data was moved around and some display data changed. Too much was changed to feasibly log it all in here. Major breaking changes or notable new features will be logged. +* New server creation page now makes significantly less AJAX calls and is much quicker to respond. +* Server and Node view pages wee modified to split tabs into individual pages to make re-themeing and modifications significantly easier, and reduce MySQL query loads on page. +* Most of the backend `UnhandledException` display errors now include a clearer error that directs admins to the program's logs. +* Table seeders for services now can be run during upgrades and will attempt to locate and update, or create new if not found in the database. +* Many structural changes to the database and `Pterodactyl\Models` classes that would flood this changelog if they were all included. All required migrations included to handle database changes. +* Clarified details for database hosts to prevent users entering invalid account details, as well as renamed tables and columns relating to it to keep things clearer. +* Updated all code to be Laravel compliant when using `env()` and moved to using `config()` throughout non `config/*.php` files. +* Subuser permissions are now stored in `Permission::listPermissions()` to make views way cleaner and make adding to views significantly cleaner. +* Attempting to reset a password for an account that does not exist no longer returns an error, rather it displays a success message. Failed resets trigger a `Pterodactyl\Events\Auth\FailedPasswordReset` event that can be caught if needed to perform other actions. +* Servers are no longer queued for deletion due to the general hassle and extra logic required. +* Updated all panel components to run on Laravel v5.4 rather than 5.3 which is EOL. +* Routes are now handled in the `routes/` folder, and use a significantly cleaner syntax. Controller names and methods have been updated as well to be clearer as well as avoid conflicts with PHP reserved keywords. +* API has been completely overhauled to use new permissions system. **Any old API keys will immediately become invalid and fail to operate properly anymore. You will need to generate new keys.** +* Cleaned up dynamic database connection setting to use a single function call from the host model. +* Deleting a server safely now continues even if the daemon reports a `HTTP/404` missing server error (requires `Daemon@0.4.0-beta.2.1`) +* Changed behavior when modifying server allocation information. You can now remove the default allocation assuming you assing a new allocation at the same time. Reduces the number of steps to change the default allocation for a server. +* Environment setting commands now attempt to auto-quote strings with spaces in them, as well as comment lines that are edited to avoid manual changes being overwritten. +* Version in footer of panel now displays correctly if panel is installed using Git rather than a download from source. +* Mobile views are now more... viewable. Fixes `col-xs-6` usage thoughout the Admin CP where it was intended to be `col-md-6`. +* Node Configuration tokens and Download tokens are stored using the cache helpers rather than a database to speed up functions and make use of auto-expiration/deletion functions. +* Old daemon routes using `/remote` have been changed to use `/daemon`, panel changes now reflect this. +* Only display servers that a user is owner of or subuser of in the Admin CP rather than all servers if the user is marked as an admin. +* Panel now sends all non-default allocations as `ALLOC_#__IP` and `ALLOC_#__PORT` to the daemon, as well as the location. + +### Added +* Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon. +* Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update. +* Users can now have a username as well as client name assigned to their account. +* Ability to create a node through the CLI using `pterodactyl:node` as well as locations via `pterodactyl:location`. +* New theme (AdminLTE) for front-end with tweaks to backend files to work properly with it. +* Add support for PhraseApp's in-context editor +* Notifications when a user is added or removed as a subuser for a server. +* New cache policy for ServerPolicy to avoid making 15+ queries per page load when confirming if a user has permission to perform an action. +* Ability to assign multiple allocations at once when creating a new server. +* New `humanReadable` macro on `File` facade that accepts a file path and returns a human readable size. (`File::humanReadable(path, precision)`) +* Added ability to edit database host details after creation on the system. +* Login attempts and pasword reset requests are now protected by invisible ReCaptcha. This feature can be disabled with a `.env` variable. +* Server listing for individual users is now searchable on the front-end. +* Servers that a user is assocaited with as a subuser are now displayed in addition to owned servers when listing users in the Admin CP. +* Ability to launch the console in a new window as an individual unit. https://s3.kelp.in/IrTyE.png +* Server listing and view in Admin CP now shows the SFTP username/Docker container name. +* Administrative server view includes link in navigation to go to server console/frontend management. +* Added new scripts for service options that allows installation of software in a privileged Docker container on the node prior to marking a server as installed. +* Added ability to reinstall a server using the currently assigned service and option. +* Added ability to change a server's service and service option, as well as change pack assignments and other management services in that regard. +* Added support for using a proxy such as Cloudflare with a node connection. Previously there was no way to tell the panel to connect over SSL without marking the Daemon as also using SSL. + +### Removed +* Removed all old theme JS and CSS folders to cleanup and avoid confusion in the future. +* Old API calls to `Server::create` will fail due to changed data structure. +* Many old routes were modified to reflect new standards in panel, and many of the controller functions being called were also modified. This shouldn't really impact anyone unless you have been digging into the code and modifying things. +* `Server::getUserDaemonSecret(Server $server)` was removed and replaced with `User::daemonSecret(Server $server)` in order to clean up models. +* `Server::getByUUID()` was replaced with `Server::byUuid()` as well as various other functions through-out the Server model. +* `Server::getHeaders()` was removed and replaced with `Server::getClient()` which returns a Guzzle Client with the correct headers already assigned. + +## v0.6.0-rc.1 +### Fixed +* `[beta.2.1]` — Fixed a bug preventing the deletion of a server. +* It is now possible to modify a server's disk limits after the server is created. +* `[beta.2.1]` — Fixes a bug causing login issues and password reset failures when reCAPTCHA is enabled. +* Fixes remote timing attack vulnerability due to hmac comparsion in API middleware. +* `[beta.2.1]` — Fixes bug requiring docker image field to be filled out when adding a service option. +* `[beta.2.1]` — Fixes inability to mark a user as a non-admin once they were assigned the role. + +### Added +* Added new scripts for service options that allows installation of software in a privileged Docker container on the node prior to marking a server as installed. +* Added ability to reinstall a server using the currently assigned service and option. +* Added ability to change a server's service and service option, as well as change pack assignments and other management services in that regard. +* Added support for using a proxy such as Cloudflare with a node connection. Previously there was no way to tell the panel to connect over SSL without marking the Daemon as also using SSL. + +### Changed +* Environment setting commands now attempt to auto-quote strings with spaces in them, as well as comment lines that are edited to avoid manual changes being overwritten. +* Version in footer of panel now displays correctly if panel is installed using Git rather than a download from source. +* Mobile views are now more... viewable. Fixes `col-xs-6` usage thoughout the Admin CP where it was intended to be `col-md-6`. +* Node Configuration tokens and Download tokens are stored using the cache helpers rather than a database to speed up functions and make use of auto-expiration/deletion functions. +* Old daemon routes using `/remote` have been changed to use `/daemon`, panel changes now reflect this. +* Only display servers that a user is owner of or subuser of in the Admin CP rather than all servers if the user is marked as an admin. + +## v0.6.0-beta.2.1 +### Fixed +* `[beta.2]` — Suspended servers now show as suspended. +* `[beta.2]` — Corrected the information when a task has not run yet. +* `[beta.2]` — Fixes filemanager 404 when editing a file within a directory. +* `[beta.2]` — Fixes exception in tasks when deleting a server. +* `[beta.2]` — Fixes bug with Terarria and Voice servers reporting a `TypeError: Service is not a constructor` in the daemon due to a missing service configuration. +* `[beta.2]` — Fixes password reset form throwing a MethodNotAllowed error when accessed. +* `[beta.2]` — Fixes invalid password bug when attempting to change account email address. +* `[beta.2]` — New attempt at fixing the issues when rendering files in the browser file editor on certain browsers. +* `[beta.2]` — Fixes broken auto-deploy time checking causing no tokens to work. +* `[beta.2]` — Fixes display of subusers after creation. +* `[beta.2]` — Fixes bug throwing model not found exception when editing an existing subuser. + +### Changed +* Deleting a server safely now continues even if the daemon reports a `HTTP/404` missing server error (requires `Daemon@0.4.0-beta.2.1`) +* Changed behavior when modifying server allocation information. You can now remove the default allocation assuming you assing a new allocation at the same time. Reduces the number of steps to change the default allocation for a server. + +### Added +* Server listing and view in Admin CP now shows the SFTP username/Docker container name. +* Administrative server view includes link in navigation to go to server console/frontend management. + +## v0.6.0-beta.2 +### Fixed +* `[beta.1]` — Fixes task management ststem not running correctly. +* `[beta.1]` — Fixes API endpoint for command sending missing the required class definition. +* `[beta.1]` — Fixes panel looking for an old compiled classfile that is no longer used. This was causing errors relating to `missing class DingoAPI` when trying to upgrade the panel. +* `[beta.1]` — Should fix render issues when trying to edit some files via the panel file editor. + +### Added +* Ability to launch the console in a new window as an individual unit. https://s3.kelp.in/IrTyE.png + +## v0.6.0-beta.1 +### Fixed +* `[pre.7]` — Fixes bug with subuser checkbox display. +* `[pre.7]` — Fixes bug with injected JS that was causing `` to be ignored in templates. +* `[pre.7]` — Fixes exception thrown when trying to delete a node due to a misnamed model. +* `[pre.7]` — Fixes username vanishing on failed login attempts. +* `[pre.7]` — Terminal is now fixed to actually output all lines, rather than leaving one hanging in neverland until the browser is resized. + +### Added +* Login attempts and pasword reset requests are now protected by invisible ReCaptcha. This feature can be disabled with a `.env` variable. +* Server listing for individual users is now searchable on the front-end. +* Servers that a user is assocaited with as a subuser are now displayed in addition to owned servers when listing users in the Admin CP. + +### Changed +* Subuser permissions are now stored in `Permission::listPermissions()` to make views way cleaner and make adding to views significantly cleaner. +* `[pre.7]` — Sidebar for file manager now is a single link rather than a dropdown. +* Attempting to reset a password for an account that does not exist no longer returns an error, rather it displays a success message. Failed resets trigger a `Pterodactyl\Events\Auth\FailedPasswordReset` event that can be caught if needed to perform other actions. +* Servers are no longer queued for deletion due to the general hassle and extra logic required. +* Updated all panel components to run on Laravel v5.4 rather than 5.3 which is EOL. +* Routes are now handled in the `routes/` folder, and use a significantly cleaner syntax. Controller names and methods have been updated as well to be clearer as well as avoid conflicts with PHP reserved keywords. +* API has been completely overhauled to use new permissions system. **Any old API keys will immediately become invalid and fail to operate properly anymore. You will need to generate new keys.** +* Cleaned up dynamic database connection setting to use a single function call from the host model. +* `[pre.7]` — Corrected a config option for spigot servers to set a boolean value as boolean, and not as a string. + +## v0.6.0-pre.7 +### Fixed +* `[pre.6]` — Addresses misconfigured console queue that was still sending data way to quickly thus causing the console to explode on some devices when large amounts of data were sent. +* `[pre.6]` — Fixes bug in allocation parsing for a node that prevented adding new allocations. +* `[pre.6]` — Fixes typo in migrations that wouldn't save custom regex for non-required variables. +* `[pre.6]` — Fixes auto-deploy checkbox on server creation causing validation error. + +## v0.6.0-pre.6 +### Fixed +* `[pre.5]` — Console based server rebuild tool now actually rebuilds the servers with the correct information. +* `[pre.5]` — Fixes typo and wrong docker contaienr for certain applications. + +### Changed +* Removed all old theme JS and CSS folders to cleanup and avoid confusion in the future. + +### Added +* `[pre.5]` — Added foreign key to `pack_id` to ensure nothing eds up breaking there. + +## v0.6.0-pre.5 +### Changed +* New theme applied to Admin CP. Many graphical changes were made, some data was moved around and some display data changed. Too much was changed to feasibly log it all in here. Major breaking changes or notable new features will be logged. +* New server creation page now makes significantly less AJAX calls and is much quicker to respond. +* Server and Node view pages wee modified to split tabs into individual pages to make re-themeing and modifications significantly easier, and reduce MySQL query loads on page. +* `[pre.4]` — Services and Pack magement overhauled to be faster, cleaner, and more extensible in the future. +* Most of the backend `UnhandledException` display errors now include a clearer error that directs admins to the program's logs. +* Table seeders for services now can be run during upgrades and will attempt to locate and update, or create new if not found in the database. +* Many structural changes to the database and `Pterodactyl\Models` classes that would flood this changelog if they were all included. All required migrations included to handle database changes. +* `[pre.4]` — Service pack files are now stored in the database rather than on the host system to make updates easier. +* Clarified details for database hosts to prevent users entering invalid account details, as well as renamed tables and columns relating to it to keep things clearer. +* Updated all code to be Laravel compliant when using `env()` and moved to using `config()` throughout non `config/*.php` files. + +### Fixed +* Fixes potential bug with invalid CIDR notation (ex: `192.168.1.1/z`) when adding allocations that could cause over 4 million records to be created at once. +* `[pre.4]` — Fixes bug preventing server updates from occurring by the system due to undefined `Auth::user()` in the event listener. +* `[pre.4]` — Fixes `Server::byUuid()` caching to actually clear the cache for *all* users, rather than the logged in user by using cache tags. +* `[pre.4]` — Fixes server listing on frontend not displaying a page selector when more than 10 servers exist. +* `[pre.4]` — Fixes non-admin users being unable to create personal API keys. +* Fixes bug where daemon was unable to register that certain games had fully booted and were ready to play on. +* Fixes bug causing MySQL user accounts to be corrupted when resetting a password via the panel. +* `[pre.4]` — Multiple clients refreshing the console no longer clears the console for all parties involved... sorry about that. +* `[pre.4]` — Fixes bug in environment setting script that would not remeber defaults and try to re-assign values. + +### Added +* Ability to assign multiple allocations at once when creating a new server. +* New `humanReadable` macro on `File` facade that accepts a file path and returns a human readable size. (`File::humanReadable(path, precision)`) +* Added ability to edit database host details after creation on the system. + +### Deprecated +* Old API calls to `Server::create` will fail due to changed data structure. +* Many old routes were modified to reflect new standards in panel, and many of the controller functions being called were also modified. This shouldn't really impact anyone unless you have been digging into the code and modifying things. + +## v0.6.0-pre.4 +### Fixed +* `[pre.3]` — Fixes bug in cache handler that doesn't cache against the user making the request. Would have allowed for users to access servers not belonging to themselves in production. +* `[pre.3]` — Fixes misnamed MySQL column that was causing the inability to delete certain port ranges from the database. +* `[pre.3]` — Fixes bug preventing rebuilding server containers through the Admin CP. + +### Added +* New cache policy for ServerPolicy to avoid making 15+ queries per page load when confirming if a user has permission to perform an action. + +## v0.6.0-pre.3 +### Fixed +* `[pre.2]` — Fixes bug where servers could not be manually deployed to nodes due to a broken SQL call. +* `[pre.2]` — Fixes inability to edit a server due to owner_id issues. +* `[pre.2]` — Fixes bug when trying to add new subusers. +* Emails sending with 'Pterodactyl Panel' as the from name. Now configurable by using `php artisan pterodactyl:mail` to update. +* `[pre.2]` — Fixes inability to delete accounts due to SQL changes. +* `[pre.2]` — Fixes bug with checking power-permissions that showed the wrong buttons. Also adds check back to sidebar to only show options a user can use. +* `[pre.2]` — Fixes allocation listing on node allocations tab as well as bug preventing deletion of port. +* `[pre.2]` — Fixes bug in services that prevented saving updated settings or creating new services. + +### Changed +* `[pre.2]` — File Manager now displays relevant information on all screen sizes, and includes better button clicking mechanics for dropdown menu. +* Reduced the number of database queries being executed when viewing a specific server. This is done by caching the query for up to 60 minutes in memcached. +* User creation emails include more information and are sent by the event listener rather than the repository. +* Account password reset emails now auto-fill the email when clicking the link. + +### Added +* Notifications when a user is added or removed as a subuser for a server. + +## v0.6.0-pre.2 +### Fixed +* `[pre.1]` — Fixes bug with database seeders that prevented correctly installing the panel. + +### Changed +* `[pre.1]` — Moved around navigation bar on fronted to make it more obvious where logout and admin buttons were, as well as use the right icon for server listing. + +## v0.6.0-pre.1 +### Added +* Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon. +* Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update. +* Users can now have a username as well as client name assigned to their account. +* Ability to create a node through the CLI using `pterodactyl:node` as well as locations via `pterodactyl:location`. +* New theme (AdminLTE) for front-end with tweaks to backend files to work properly with it. +* Add support for PhraseApp's in-context editor + +### Fixed +* Bug causing error logs to be spammed if someone timed out on an ajax based page. +* Fixes edge case where specific server names could cause daemon errors due to an invalid SFTP username being created by the panel. +* Fixes sessions being removed on browser close, and set sessions to idle for up to 3 hours before being marked as expired. + +### Changed +* Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields. +* User model now defines mass assignment fields using `$fillable` rather than `$guarded`. +* 2FA checkpoint on login is now its own page, and not an AJAX based call. Improves security on that front. +* Updated Server model code to be more efficient, as well as make life easier for backend changes and work. + +### Deprecated +* `Server::getUserDaemonSecret(Server $server)` was removed and replaced with `User::daemonSecret(Server $server)` in order to clean up models. +* `Server::getByUUID()` was replaced with `Server::byUuid()` as well as various other functions through-out the Server model. +* `Server::getHeaders()` was removed and replaced with `Server::getClient()` which returns a Guzzle Client with the correct headers already assigned. + +## v0.5.6 (Bodacious Boreopterus) +### Added +* Added the following languages: Estonian `et`, Dutch `nl`, Norwegian `nb` (partial), Romanian `ro`, and Russian `ru`. Interested in helping us translate the panel into more languages, or improving existing translations? Contact us on Discord and let us know. +* Added missing `strings.password` to language file for English. +* Allow listing of users from the API by passing either the user ID or their email. + +### Fixed +* Fixes bug where assigning a variable a default value (or valid value) of `0` would cause the panel to reject the value thinking it did not exist. +* Addresses potential for crash by limiting total ports that can be assigned per-range to 2000. +* Fixes server names requiring at minimum 4 characters. Name can now be 1 to 200 characters long. :pencil2: +* Fixes bug that would allow adding the owner of a server as a subuser for that same server. +* Fixes bug that would allow creating multiple subusers with the same email address. +* Fixes bug where Sponge servers were improperly tagged as a spigot server in the daemon causing issues when booting or modifying configuration files. +* Use transpiled ES6 -> ES5 filemanager code in browsers. +* Fixes service option name displaying the name of a nwly added variable after the variable is added and until the page is refreshed. (see #208) + +### Changed +* Filemanager and EULA checking javascript is now written in pure ES6 code rather than as a blade-syntax template. This allows the use of babel to transpile into ES5 as a minified version. + +## v0.5.5 (Bodacious Boreopterus) +### Added +* New API route to return allocations given a server ID. This adds support for a community-driven WHMCS module :rocket: available [here](https://github.com/hammerdawn/Pterodactyl-WHMCS). + +### Fixed +* Fixes subuser display when trying to edit an existing subuser. + +## v0.5.4 (Bodacious Boreopterus) +### Added +* Changing node configuration values now automatically makes a call to the daemon and updates the configuration there. Changing daemon tokens now does not require any intervention, and takes effect immediately. SSL & Port configurations will still require a daemon reboot. +* New button in file manager that triggers the right click menu to enable support on mobile devices and those who cannot right click (blessed be them). +* Support for filtering users when listing all users on the system. +* Container ID and User ID on the daemon are now shown when viewing a server in the panel. + +### Changed +* File uploads now account for a maximum file size that is assigned for the daemon, and gives cleaner errors when that limit is reached. +* File upload limit can now be controlled from the panel. +* Updates regex and default values for some Minecraft services to reflect current technology. + +### Fixed +* Fixes potential for generated password to not meet own validation requirements. +* Fixes some regex checking issues with newer versions of Minecraft. + +## v0.5.3 (Bodacious Boreopterus) +### Fixed +* Fixed an error that occurred when viewing a node listing when no nodes were created yet due to a mis-declared variable. Also fixes a bug that would have all nodes trying to connect to the daemon using the same secret token on the node listing, causing only the last node to display properly. +* Fixes a bug that displayed the panel version rather than the daemon version when viewing a node. +* Fixes a multiplicator being applied to an overallocation field rather than a storage space field when adding a node. + +### Changed +* Added a few new configuration variables for nodes to the default config, as well as a variable that will be used in future versions of the daemon. + +## v0.5.2 (Bodacious Boreopterus) +### Fixed +* Time axis on server graphs is corrected to show the minutes rather than the current month. +* Node deletion now works correctly and deletes allocations as well. +* Fixes a bug that would leave orphaned databases on the system if there was an error during creation. +* Fixes an issue that could occur if a UUID contained `#e#` formatting within it when it comes to creating databases. +* Fixed node status display to account for updated daemon security changes. +* Fixes default language being selected as German (defaults to English now). +* Fixes bug preventing the deletion of database servers. + +### Changed +* Using `node:` when filtering servers now properly filters the servers by node name, rather than looking for the node ID. +* Using `owner:` when filtering servers now properly filters by the owner's email rather than ID. +* Added some quick help buttons to the admin index page for getting support or checking the documentation. +* Panel now displays `Pterodactyl Panel` as the company name if one is not set. + +### Added +* Added basic information about the daemon when viewing a node, including the host OS and version, CPU count, and the daemon version. +* Added version checking for the daemon and panel that alerts admins when daemons or the panel is out of date. +* Added multiplicator support to certain memory and disk fields that allow users to enter `10g` and have it converted to MB automatically. + +## v0.5.1 (Bodacious Boreopterus) +### Fixed +* Fixes a bug that allowed a user to bypass 2FA authentication if using the correct username and password for an account. + ## v0.5.0 (Bodacious Boreopterus) After nearly a month in the works, version `v0.5.0` is finally here! 🎉 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..d00264999 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at support@pterodactyl.io. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d1a6faac..417a5364f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,12 @@ # Contributing We're glad you want to help us out and make this panel the best that it can be! We have a few simple things to follow when making changes to files and adding new features. -### License Agreement -First and foremost, this project is licensed under a MIT license. In order for us to accept code from you we need a signed and valid Contributor License Agreement ("CLA") on file. If you do not have one signed we will need you to fill it out. You can find a copy of the CLA [here](http://static.s3.pterodactyl.io/PterodactylCLA.pdf). +### Code Guidelines +*This section is still under construction.* -Any pull requests created without a CLA on file will be held until one is submitted or closed at our discression. Once you have a CLA on file we will accept Pull Requests to any repository. +We are a `PSR-4` and `PSR-0` compliant project, so please follow those guidelines at a minimum. In addition, StyleCI runs on all of our code to ensure the formatting is standardized across everything. Please follow the existing code formatting, I will write up more detailed documentation at a later time. + +In addition, all functions must be properly Doc-Block'd. ### Responsible Disclosure This is a fairly in-depth project, and makes use of a lot of parts. We strive to keep everything as secure as possible, and welcome you to take a look into it yourself. We do ask that you be considerate of others who are using the software and not publicly disclose security issues without contacting us first by email. @@ -16,7 +18,4 @@ If you've found what you believe is a security issue please email us at `support ### Where to find Us You can find us in a couple places online. First and foremost, we're active right here on Github. If you encounter a bug or other problem open an issue on here for us to take a look at it. We also accept feature requests here as well. -You can also find us on [Discord](https://discord.gg/0gYt8oU8QOkDhKLS) or our [community forums](https://community.pterodactyl.io/). In the event that you need to get in contact with us privately feel free to contact us at `support@pterodactyl.io`. Try not to email us with requests for support regarding the panel, we'll probably just direct you to our forums or here. - -### Code Guidelines -We honestly haven't gotten around to standardizing our code format, we'll get that happening very soon. In the mean time, try to emulate the current formatting. We try to stick with how Laravel code is formatted. +You can also find us on [Discord](https://pterodactyl.io/discord) or our [community forums](https://forums.pterodactyl.io/). In the event that you need to get in contact with us privately feel free to contact us at `support@pterodactyl.io`. Try not to email us with requests for support regarding the panel, we'll probably just direct you to our forums or here. diff --git a/LICENSE.md b/LICENSE.md index b4b61b7c2..929536020 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ # The MIT License (MIT) ``` -Copyright (c) 2015 - 2016 Dane Everitt +Copyright (c) 2015 - 2017 Dane Everitt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 88c02a171..393dff90c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ +[![Logo Image](https://cdn.pterodactyl.io/logos/Banner%20Logo%20Black@2x.png)](https://pterodactyl.io) + ## Pterodactyl Panel -Pterodactyl is the free game server management panel designed by users, for users. Featuring support for Vanilla Minecraft, Spigot, Source Dedicated Servers, BungeeCord, and many more. Pterodactyl is built on the `Laravel PHP Framework (v5.3)`. +Pterodactyl Panel is the free, open-source, game agnostic, self-hosted control panel for users, networks, and game service providers. Pterodactyl supports games and servers such as Minecraft (including Spigot, Bungeecord, and Sponge), ARK: Evolution Evolved, CS:GO, Team Fortress 2, Insurgency, Teamspeak 3, Mumble, and many more. Control all of your games from one unified interface. ## Support & Documentation -Support for using Pterodactyl can be found on our [wiki](https://github.com/Pterodactyl/Panel/wiki) or on our [Discord chat](https://discord.gg/0gYt8oU8QOkDhKLS). +Support for using Pterodactyl can be found on our [Documentation Website](https://docs.pterodactyl.io), our [Discord Chat](https://discord.gg/QRDZvVm), or via our [Forums](https://forums.pterodactyl.io). ## License ``` -Copyright (c) 2015 - 2016 Dane Everitt +Copyright (c) 2015 - 2017 Dane Everitt . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -33,11 +35,13 @@ A huge thanks to [PhraseApp](https://phraseapp.com) who provide us the software Ace Editor - [license](https://github.com/ajaxorg/ace/blob/master/LICENSE) - [homepage](https://ace.c9.io) +AdminLTE - [license](https://github.com/almasaeed2010/AdminLTE/blob/master/LICENSE) - [homepage](https://almsaeedstudio.com) + Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICENSE) - [homepage](http://daneden.github.io/animate.css/) -Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/) +AnsiUp - [license](https://github.com/drudru/ansi_up/blob/master/Readme.md#license) - [homepage](https://github.com/drudru/ansi_up) -BinaryJS - [license](https://github.com/binaryjs/binaryjs/blob/master/LICENSE) - [homepage](http://binaryjs.com) +Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/) Bootstrap - [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) - [homepage](http://getbootstrap.com) @@ -49,16 +53,18 @@ FontAwesome - [license](http://fontawesome.io/license/) - [homepage](http://font FontAwesome Animations - [license](https://github.com/l-lin/font-awesome-animation#license) - [homepage](https://github.com/l-lin/font-awesome-animation) -FuelUX - [license](https://github.com/ExactTarget/fuelux/blob/master/LICENSE) - [homepage](http://getfuelux.com) - jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [homepage](http://jquery.com) -jQuery Terminal - [license](https://github.com/jcubic/jquery.terminal/blob/master/LICENSE) - [homepage](http://terminal.jcubic.pl) +Laravel Framework - [license](https://github.com/laravel/framework/blob/5.4/LICENSE.md) - [homepage](https://laravel.com) Lodash - [license](https://github.com/lodash/lodash/blob/master/LICENSE) - [homepage](https://lodash.com/) +Select2 - [license](https://github.com/select2/select2/blob/master/LICENSE.md) - [homepage](https://select2.github.io) + Socket.io - [license](https://github.com/socketio/socket.io/blob/master/LICENSE) - [homepage](http://socket.io) +Socket.io File Upload - [license](https://github.com/vote539/socketio-file-upload/blob/master/server.js#L1-L27) - [homepage](https://github.com/vote539/socketio-file-upload) + SweetAlert - [license](https://github.com/t4t5/sweetalert/blob/master/LICENSE) - [homepage](http://t4t5.github.io/sweetalert/) Typeahead — [license](https://github.com/bassjobsen/Bootstrap-3-Typeahead/blob/master/bootstrap3-typeahead.js) — [homepage](https://github.com/bassjobsen/Bootstrap-3-Typeahead) @@ -68,6 +74,6 @@ Some Javascript and CSS used within the panel is licensed under a `MIT` or `Apac Some images used within Pterodactyl are Copyright (c) their respective owners. -`/public/images/403.jpg` is licensed under a [CC BY 2.0](http://creativecommons.org/licenses/by/2.0/) by [BigTallGuy](http://flickr.com/photos/bigtallguy/) +`/public/themes/default/images/403.jpg` is licensed under a [CC BY 2.0](http://creativecommons.org/licenses/by/2.0/) by [BigTallGuy](http://flickr.com/photos/bigtallguy/) -`/public/images/404.jpg` is licensed under a [CC BY-SA 2.0](http://creativecommons.org/licenses/by-sa/2.0/) by [nicsuzor](http://flickr.com/photos/nicsuzor/) +`/public/themes/default/images/404.jpg` is licensed under a [CC BY-SA 2.0](http://creativecommons.org/licenses/by-sa/2.0/) by [nicsuzor](http://flickr.com/photos/nicsuzor/) diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 000000000..ed4b5bd4c --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,21 @@ +Vagrant.configure("2") do |config| + config.vm.box = "ubuntu/xenial64" + + config.vm.synced_folder "./", "/var/www/html/pterodactyl", + owner: "www-data", group: "www-data" + + #config.vm.provision :file, source: ".dev/vagrant/pterdactyl.conf", destination: "/etc/nginx/sites-available/pterodactyl.conf" + #config.vm.provision :file, source: ".dev/vagrant/pteroq.service", destination: "/etc/systemd/system/pteroq.service" + #config.vm.provision :file, source: ".dev/vagrant/mailhog.service", destination: "/etc/systemd/system/mailhog.service" + #config.vm.provision :file, source: ".dev/vagrant/.env", destination: "/var/www/html/pterodactyl/.env" + config.vm.provision :shell, path: ".dev/vagrant/provision.sh" + + config.vm.network :private_network, ip: "192.168.50.2" + config.vm.network :forwarded_port, guest: 80, host: 50080 + config.vm.network :forwarded_port, guest: 8025, host: 58025 + config.vm.network :forwarded_port, guest: 3306, host: 53306 + + # Config for the vagrant-dns plugin (https://github.com/BerlinVagrant/vagrant-dns) + config.dns.tld = "app" + config.dns.patterns = [/^pterodactyl.app$/] +end diff --git a/app/Console/Commands/AddLocation.php b/app/Console/Commands/AddLocation.php new file mode 100644 index 000000000..d9da92466 --- /dev/null +++ b/app/Console/Commands/AddLocation.php @@ -0,0 +1,75 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Console\Commands; + +use Illuminate\Console\Command; +use Pterodactyl\Repositories\LocationRepository; + +class AddLocation extends Command +{ + protected $data = []; + + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'pterodactyl:location + {--short= : The shortcode name of this location (ex. us1).} + {--long= : A longer description of this location.}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Creates a new location on the system via the CLI.'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $this->data['short'] = (is_null($this->option('short'))) ? $this->ask('Location Short Code') : $this->option('short'); + $this->data['long'] = (is_null($this->option('long'))) ? $this->ask('Location Description') : $this->option('long'); + + $repo = new LocationRepository; + $id = $repo->create($this->data); + + $this->info('Location ' . $this->data['short'] . ' created with ID: ' . $id); + } +} diff --git a/app/Console/Commands/AddNode.php b/app/Console/Commands/AddNode.php new file mode 100644 index 000000000..0aac540c0 --- /dev/null +++ b/app/Console/Commands/AddNode.php @@ -0,0 +1,112 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Console\Commands; + +use Illuminate\Console\Command; +use Pterodactyl\Models\Location; +use Pterodactyl\Repositories\NodeRepository; + +class AddNode extends Command +{ + protected $data = []; + + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'pterodactyl:node + {--name= : Name of the node.} + {--location= : The shortcode of the location to add this node to.} + {--fqdn= : The fully-qualified domain for the node.} + {--ssl= : Should the daemon use SSL for connections (T/F).} + {--memory= : The total memory available on this node for servers.} + {--disk= : The total disk space available on this node for servers.} + {--daemonBase= : The directory in which server files will be stored.} + {--daemonListen= : The port the daemon will listen on for connections.} + {--daemonSFTP= : The port to be used for SFTP conncetions to the daemon.}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Adds a new node to the system via the CLI.'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $locations = Location::all(['id', 'short', 'long']); + + $this->data['name'] = (is_null($this->option('name'))) ? $this->ask('Node Name') : $this->option('name'); + + if (is_null($this->option('location'))) { + $this->table(['ID', 'Short Code', 'Description'], $locations->toArray()); + $selectedLocation = $this->anticipate('Node Location (Short Name)', $locations->pluck('short')->toArray()); + } else { + $selectedLocation = $this->option('location'); + } + + $this->data['location_id'] = $locations->where('short', $selectedLocation)->first()->id; + + if (is_null($this->option('fqdn'))) { + $this->line('Please enter domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node.'); + $this->data['fqdn'] = $this->ask('Fully Qualified Domain Name'); + } else { + $this->data['fqdn'] = $this->option('fqdn'); + } + + $useSSL = (is_null($this->option('ssl'))) ? $this->confirm('Use SSL', true) : $this->option('ssl'); + + $this->data['scheme'] = ($useSSL) ? 'https' : 'http'; + $this->data['memory'] = (is_null($this->option('memory'))) ? $this->ask('Total Memory (in MB)') : $this->option('memory'); + $this->data['memory_overallocate'] = 0; + $this->data['disk'] = (is_null($this->option('disk'))) ? $this->ask('Total Disk Space (in MB)') : $this->option('disk'); + $this->data['disk_overallocate'] = 0; + $this->data['public'] = 1; + $this->data['daemonBase'] = (is_null($this->option('daemonBase'))) ? $this->ask('Daemon Server File Location', '/srv/daemon-data') : $this->option('daemonBase'); + $this->data['daemonListen'] = (is_null($this->option('daemonListen'))) ? $this->ask('Daemon Listening Port', 8080) : $this->option('daemonListen'); + $this->data['daemonSFTP'] = (is_null($this->option('daemonSFTP'))) ? $this->ask('Daemon SFTP Port', 2022) : $this->option('daemonSFTP'); + + $repo = new NodeRepository; + $id = $repo->create($this->data); + + $this->info('Node created with ID: ' . $id); + } +} diff --git a/app/Listeners/DeleteServerListener.php b/app/Console/Commands/CleanServiceBackup.php similarity index 54% rename from app/Listeners/DeleteServerListener.php rename to app/Console/Commands/CleanServiceBackup.php index 6a7833504..0a6a3e272 100644 --- a/app/Listeners/DeleteServerListener.php +++ b/app/Console/Commands/CleanServiceBackup.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,44 +21,54 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Listeners; + +namespace Pterodactyl\Console\Commands; use Carbon; +use Storage; +use Illuminate\Console\Command; -use Pterodactyl\Events\ServerDeleted; -use Illuminate\Foundation\Bus\DispatchesJobs; - -use Pterodactyl\Jobs\SuspendServer; -use Pterodactyl\Jobs\DeleteServer; - -class DeleteServerListener +class CleanServiceBackup extends Command { - - use DispatchesJobs; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'pterodactyl:cleanservices'; /** - * Create the event listener. + * The console command description. + * + * @var string + */ + protected $description = 'Cleans .bak files assocaited with service backups whene editing files through the panel.'; + + /** + * Create a new command instance. * * @return void */ public function __construct() { - // + parent::__construct(); } /** - * Handle the event. + * Execute the console command. * - * @param DeleteServerEvent $event - * @return void + * @return mixed */ - public function handle(ServerDeleted $event) + public function handle() { - $this->dispatch((new SuspendServer($event->server))->onQueue(env('QUEUE_HIGH', 'high'))); - $this->dispatch( - (new DeleteServer($event->server)) - ->delay(Carbon::now()->addMinutes(env('APP_DELETE_MINUTES', 10))) - ->onQueue(env('QUEUE_STANDARD', 'standard')) - ); + $files = Storage::files('services/.bak'); + + foreach ($files as $file) { + $lastModified = Carbon::createFromTimestamp(Storage::lastModified($file)); + if ($lastModified->diffInMinutes(Carbon::now()) > 5) { + $this->info('Deleting ' . $file); + Storage::delete($file); + } + } } } diff --git a/app/Console/Commands/ClearServices.php b/app/Console/Commands/ClearServices.php index 626d7720f..db62d268c 100644 --- a/app/Console/Commands/ClearServices.php +++ b/app/Console/Commands/ClearServices.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,10 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Console\Commands; use DB; - use Illuminate\Console\Command; class ClearServices extends Command @@ -60,8 +60,7 @@ class ClearServices extends Command */ public function handle() { - - if (!$this->confirm('This is a destructive operation, are you sure you wish to continue?')) { + if (! $this->confirm('This is a destructive operation, are you sure you wish to continue?')) { $this->error('Canceling.'); exit(); } diff --git a/app/Console/Commands/ClearTasks.php b/app/Console/Commands/ClearTasks.php index 4f2181ca0..569caf028 100644 --- a/app/Console/Commands/ClearTasks.php +++ b/app/Console/Commands/ClearTasks.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,19 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Console\Commands; -use DB; use Carbon; use Pterodactyl\Models; use Illuminate\Console\Command; use Illuminate\Foundation\Bus\DispatchesJobs; -use Pterodactyl\Jobs\SendScheduledTask; - class ClearTasks extends Command { - use DispatchesJobs; /** @@ -67,7 +64,7 @@ class ClearTasks extends Command */ public function handle() { - $entries = Models\TaskLog::where('run_time', '<=', Carbon::now()->subHours(env('APP_CLEAR_TASKLOG', 720))->toAtomString())->get(); + $entries = Models\TaskLog::where('run_time', '<=', Carbon::now()->subHours(config('pterodactyl.tasks.clear_log'))->toAtomString())->get(); $this->info(sprintf('Preparing to delete %d old task log entries.', count($entries))); $bar = $this->output->createProgressBar(count($entries)); diff --git a/app/Console/Commands/Inspire.php b/app/Console/Commands/Inspire.php index 6efa5aa73..b579f2c86 100644 --- a/app/Console/Commands/Inspire.php +++ b/app/Console/Commands/Inspire.php @@ -28,6 +28,6 @@ class Inspire extends Command */ public function handle() { - $this->comment(PHP_EOL.Inspiring::quote().PHP_EOL); + $this->comment(PHP_EOL . Inspiring::quote() . PHP_EOL); } } diff --git a/app/Console/Commands/MakeUser.php b/app/Console/Commands/MakeUser.php index a1d507b79..05803dd3b 100644 --- a/app/Console/Commands/MakeUser.php +++ b/app/Console/Commands/MakeUser.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,11 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Console\Commands; -use Hash; use Illuminate\Console\Command; - use Pterodactyl\Repositories\UserRepository; class MakeUser extends Command @@ -36,6 +35,9 @@ class MakeUser extends Command * @var string */ protected $signature = 'pterodactyl:user + {--firstname= : First name to use for this account.} + {--lastname= : Last name to use for this account.} + {--username= : Username to use for this account.} {--email= : Email address to use for this account.} {--password= : Password to assign to the user.} {--admin= : Boolean flag for if user should be an admin.}'; @@ -64,19 +66,23 @@ class MakeUser extends Command */ public function handle() { - $email = is_null($this->option('email')) ? $this->ask('Email') : $this->option('email'); - $password = is_null($this->option('password')) ? $this->secret('Password') : $this->option('password'); + $data['name_first'] = is_null($this->option('firstname')) ? $this->ask('First Name') : $this->option('firstname'); + $data['name_last'] = is_null($this->option('lastname')) ? $this->ask('Last Name') : $this->option('lastname'); + $data['username'] = is_null($this->option('username')) ? $this->ask('Username') : $this->option('username'); + $data['email'] = is_null($this->option('email')) ? $this->ask('Email') : $this->option('email'); + $data['password'] = is_null($this->option('password')) ? $this->secret('Password') : $this->option('password'); $password_confirmation = is_null($this->option('password')) ? $this->secret('Confirm Password') : $this->option('password'); - if ($password !== $password_confirmation) { + if ($data['password'] !== $password_confirmation) { return $this->error('The passwords provided did not match!'); } - $admin = is_null($this->option('admin')) ? $this->confirm('Is this user a root administrator?') : $this->option('admin'); + $data['root_admin'] = is_null($this->option('admin')) ? $this->confirm('Is this user a root administrator?') : $this->option('admin'); try { $user = new UserRepository; - $user->create($email, $password, $admin); + $user->create($data); + return $this->info('User successfully created.'); } catch (\Exception $ex) { return $this->error($ex->getMessage()); diff --git a/app/Console/Commands/RebuildServer.php b/app/Console/Commands/RebuildServer.php new file mode 100644 index 000000000..2177b112a --- /dev/null +++ b/app/Console/Commands/RebuildServer.php @@ -0,0 +1,146 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Console\Commands; + +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Server; +use Illuminate\Console\Command; + +class RebuildServer extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'pterodactyl:rebuild + {--all} + {--node= : Id of node to rebuild all servers on.} + {--server= : UUID of server to rebuild.}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Rebuild docker containers for a server or multiple servers.'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + if ($this->option('all')) { + $servers = Server::all(); + } elseif ($this->option('node')) { + $servers = Server::where('node_id', $this->option('node'))->get(); + } elseif ($this->option('server')) { + $servers = Server::where('id', $this->option('server'))->get(); + } else { + $this->error('You must pass a flag to determine which server(s) to rebuild.'); + + return; + } + + $servers->load('node', 'service', 'option.variables', 'pack'); + + $this->line('Beginning processing, do not exit this script.'); + $bar = $this->output->createProgressBar(count($servers)); + $results = collect([]); + foreach ($servers as $server) { + try { + $environment = $server->option->variables->map(function ($item, $key) use ($server) { + $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + + return [ + 'variable' => $item->env_variable, + 'value' => (! is_null($display)) ? $display : $item->default_value, + ]; + }); + + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ + 'json' => [ + 'build' => [ + 'image' => $server->image, + 'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup]), + ], + 'service' => [ + 'type' => $server->service->folder, + 'option' => $server->option->tag, + 'pack' => ! is_null($server->pack) ? $server->pack->uuid : null, + ], + ], + ]); + + $results = $results->merge([ + $server->uuid => [ + 'status' => 'info', + 'messages' => [ + '[✓] Processed rebuild request for ' . $server->uuid, + ], + ], + ]); + } catch (\Exception $ex) { + $results = $results->merge([ + $server->uuid => [ + 'status' => 'error', + 'messages' => [ + '[✗] Failed to process rebuild request for ' . $server->uuid, + $ex->getMessage(), + ], + ], + ]); + } + + $bar->advance(); + } + + $bar->finish(); + $console = $this; + + $this->line("\n"); + $results->each(function ($item, $key) use ($console) { + foreach ($item['messages'] as $line) { + $console->{$item['status']}($line); + } + }); + $this->line("\nCompleted rebuild command processing."); + } +} diff --git a/app/Console/Commands/RunTasks.php b/app/Console/Commands/RunTasks.php index 3047dc54e..6c3ccc6c9 100644 --- a/app/Console/Commands/RunTasks.php +++ b/app/Console/Commands/RunTasks.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,19 +21,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Console\Commands; -use DB; use Carbon; -use Pterodactyl\Models; +use Pterodactyl\Models\Task; use Illuminate\Console\Command; -use Illuminate\Foundation\Bus\DispatchesJobs; - use Pterodactyl\Jobs\SendScheduledTask; +use Illuminate\Foundation\Bus\DispatchesJobs; class RunTasks extends Command { - use DispatchesJobs; /** @@ -67,14 +65,14 @@ class RunTasks extends Command */ public function handle() { - $tasks = Models\Task::where('queued', 0)->where('active', 1)->where('next_run', '<=', Carbon::now()->toAtomString())->get(); + $tasks = Task::where('queued', false)->where('active', true)->where('next_run', '<=', Carbon::now()->toAtomString())->get(); $this->info(sprintf('Preparing to queue %d tasks.', count($tasks))); $bar = $this->output->createProgressBar(count($tasks)); foreach ($tasks as &$task) { $bar->advance(); - $this->dispatch((new SendScheduledTask(Models\Server::findOrFail($task->server), $task))->onQueue(env('QUEUE_LOW', 'low'))); + $this->dispatch((new SendScheduledTask($task))->onQueue(config('pterodactyl.queues.low'))); } $bar->finish(); diff --git a/app/Console/Commands/ShowVersion.php b/app/Console/Commands/ShowVersion.php index 0dcce04a1..199a905b1 100644 --- a/app/Console/Commands/ShowVersion.php +++ b/app/Console/Commands/ShowVersion.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,8 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Console\Commands; +use Version; use Illuminate\Console\Command; class ShowVersion extends Command @@ -58,6 +60,6 @@ class ShowVersion extends Command */ public function handle() { - $this->info('You are running Pterodactyl Panel ' . config('app.version')); + $this->info('You are running Pterodactyl Panel v' . Version::getCurrentPanel() . ' (' . ((Version::isLatestPanel()) ? 'Up to Date' : 'Latest: ' . Version::getDaemon()) . ')'); } } diff --git a/app/Console/Commands/UpdateEmailSettings.php b/app/Console/Commands/UpdateEmailSettings.php index f6dd5a304..1e316d6cb 100644 --- a/app/Console/Commands/UpdateEmailSettings.php +++ b/app/Console/Commands/UpdateEmailSettings.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Console\Commands; use Illuminate\Console\Command; @@ -35,6 +36,7 @@ class UpdateEmailSettings extends Command protected $signature = 'pterodactyl:mail {--driver=} {--email=} + {--from-name=} {--host=} {--port=} {--username=} @@ -66,7 +68,7 @@ class UpdateEmailSettings extends Command { $variables = []; $file = base_path() . '/.env'; - if (!file_exists($file)) { + if (! file_exists($file)) { $this->error('Missing environment file! It appears that you have not installed this panel correctly.'); exit(); } @@ -75,42 +77,43 @@ class UpdateEmailSettings extends Command $this->table([ 'Option', - 'Description' + 'Description', ], [ [ 'smtp', - 'SMTP Server Email' + 'SMTP Server Email', ], [ 'mail', - 'PHP\'s Internal Mail Server' + 'PHP\'s Internal Mail Server', ], [ 'mailgun', - 'Mailgun Email Service' + 'Mailgun Email Service', ], [ 'mandrill', - 'Mandrill Transactional Email Service' + 'Mandrill Transactional Email Service', ], [ 'postmark', - 'Postmark Transactional Email Service' - ] + 'Postmark Transactional Email Service', + ], ]); + $variables['MAIL_DRIVER'] = is_null($this->option('driver')) ? $this->choice('Which email driver would you like to use?', [ 'smtp', 'mail', 'mailgun', 'mandrill', - 'postmark' + 'postmark', ]) : $this->option('driver'); switch ($variables['MAIL_DRIVER']) { case 'smtp': - $variables['MAIL_HOST'] = is_null($this->option('host')) ? $this->ask('SMTP Host (e.g smtp.google.com)') : $this->option('host'); - $variables['MAIL_PORT'] = is_null($this->option('port')) ? $this->anticipate('SMTP Host Port (e.g 587)', ['587']) : $this->option('port'); - $variables['MAIL_USERNAME'] = is_null($this->option('username')) ? $this->ask('SMTP Username') : $this->option('password'); + $variables['MAIL_HOST'] = is_null($this->option('host')) ? $this->ask('SMTP Host (e.g smtp.google.com)', config('mail.host')) : $this->option('host'); + $variables['MAIL_PORT'] = is_null($this->option('port')) ? $this->anticipate('SMTP Host Port (e.g 587)', ['587', config('mail.port')], config('mail.port')) : $this->option('port'); + $variables['MAIL_USERNAME'] = is_null($this->option('username')) ? $this->ask('SMTP Username', config('mail.username')) : $this->option('password'); $variables['MAIL_PASSWORD'] = is_null($this->option('password')) ? $this->secret('SMTP Password') : $this->option('password'); break; case 'mail': @@ -126,7 +129,7 @@ class UpdateEmailSettings extends Command $variables['MAIL_DRIVER'] = 'smtp'; $variables['MAIL_HOST'] = 'smtp.postmarkapp.com'; $variables['MAIL_PORT'] = 587; - $variables['MAIL_USERNAME'] = is_null($this->option('username')) ? $this->ask('Postmark API Token') : $this->option('username'); + $variables['MAIL_USERNAME'] = is_null($this->option('username')) ? $this->ask('Postmark API Token', config('mail.username')) : $this->option('username'); $variables['MAIL_PASSWORD'] = $variables['MAIL_USERNAME']; break; default: @@ -135,14 +138,19 @@ class UpdateEmailSettings extends Command break; } - $variables['MAIL_FROM'] = is_null($this->option('email')) ? $this->ask('Email address emails should originate from') : $this->option('email'); + $variables['MAIL_FROM'] = is_null($this->option('email')) ? $this->ask('Email address emails should originate from', config('mail.from.address')) : $this->option('email'); + $variables['MAIL_FROM_NAME'] = is_null($this->option('from-name')) ? $this->ask('Name emails should appear to be from', config('mail.from.name')) : $this->option('from-name'); + $variables['MAIL_FROM_NAME'] = '"' . $variables['MAIL_FROM_NAME'] . '"'; $variables['MAIL_ENCRYPTION'] = 'tls'; $bar = $this->output->createProgressBar(count($variables)); $this->line('Writing new email environment configuration to file.'); foreach ($variables as $key => $value) { - $newValue = $key . '=' . $value; + if (str_contains($value, ' ') && ! str_contains($value, '"')) { + $value = '"' . $value . '"'; + } + $newValue = $key . '=' . $value . ' # DO NOT EDIT! set using pterodactyl:mail'; if (preg_match_all('/^' . $key . '=(.*)$/m', $envContents) < 1) { $envContents = $envContents . "\n" . $newValue; @@ -154,6 +162,9 @@ class UpdateEmailSettings extends Command file_put_contents($file, $envContents); $bar->finish(); + + $this->line('Updating evironment configuration cache file.'); + $this->call('config:cache'); echo "\n"; } } diff --git a/app/Console/Commands/UpdateEnvironment.php b/app/Console/Commands/UpdateEnvironment.php index 7afa3e7e2..6252a2824 100644 --- a/app/Console/Commands/UpdateEnvironment.php +++ b/app/Console/Commands/UpdateEnvironment.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Console\Commands; use Uuid; @@ -40,6 +41,9 @@ class UpdateEnvironment extends Command {--dbuser=} {--dbpass=} {--url=} + {--driver=} + {--session-driver=} + {--queue-driver=} {--timezone=}'; /** @@ -66,10 +70,9 @@ class UpdateEnvironment extends Command */ public function handle() { - $variables = []; $file = base_path() . '/.env'; - if (!file_exists($file)) { + if (! file_exists($file)) { $this->error('Missing environment file! It appears that you have not installed this panel correctly.'); exit(); } @@ -77,39 +80,37 @@ class UpdateEnvironment extends Command $envContents = file_get_contents($file); $this->info('Simply leave blank and press enter to fields that you do not wish to update.'); - if (!env('SERVICE_AUTHOR', false)) { + if (is_null(config('pterodactyl.service.author', null))) { $this->info('No service author set, setting one now.'); - $variables['SERVICE_AUTHOR'] = env('SERVICE_AUTHOR', (string) Uuid::generate(4)); + $variables['SERVICE_AUTHOR'] = (string) Uuid::generate(4); } - if (!env('QUEUE_STANDARD', false) || !env('QUEUE_DRIVER', false)) { - $this->info('Setting default queue settings.'); - $variables['QUEUE_DRIVER'] = env('QUEUE_DRIVER', 'database'); - $variables['QUEUE_HIGH'] = env('QUEUE_HIGH', 'high'); - $variables['QUEUE_STANDARD'] = env('QUEUE_STANDARD', 'standard'); - $variables['QUEUE_LOW'] = env('QUEUE_LOW', 'low'); + if (isset($variables['APP_THEME'])) { + if ($variables['APP_THEME'] === 'default') { + $variables['APP_THEME'] = 'pterodactyl'; + } } if (is_null($this->option('dbhost'))) { - $variables['DB_HOST'] = $this->anticipate('Database Host', [ 'localhost', '127.0.0.1', env('DB_HOST') ], env('DB_HOST')); + $variables['DB_HOST'] = $this->anticipate('Database Host', ['localhost', '127.0.0.1', config('database.connections.mysql.host')], config('database.connections.mysql.host')); } else { $variables['DB_HOST'] = $this->option('dbhost'); } if (is_null($this->option('dbport'))) { - $variables['DB_PORT'] = $this->anticipate('Database Port', [ 3306, env('DB_PORT') ], env('DB_PORT')); + $variables['DB_PORT'] = $this->anticipate('Database Port', [3306, config('database.connections.mysql.port')], config('database.connections.mysql.port')); } else { $variables['DB_PORT'] = $this->option('dbport'); } if (is_null($this->option('dbname'))) { - $variables['DB_DATABASE'] = $this->anticipate('Database Name', [ 'pterodactyl', 'homestead', ENV('DB_DATABASE') ], env('DB_DATABASE')); + $variables['DB_DATABASE'] = $this->anticipate('Database Name', ['pterodactyl', 'homestead', config('database.connections.mysql.database')], config('database.connections.mysql.database')); } else { $variables['DB_DATABASE'] = $this->option('dbname'); } if (is_null($this->option('dbuser'))) { - $variables['DB_USERNAME'] = $this->anticipate('Database Username', [ ENV('DB_DATABASE') ], env('DB_USERNAME')); + $variables['DB_USERNAME'] = $this->anticipate('Database Username', [config('database.connections.mysql.username')], config('database.connections.mysql.username')); } else { $variables['DB_USERNAME'] = $this->option('dbuser'); } @@ -122,23 +123,73 @@ class UpdateEnvironment extends Command } if (is_null($this->option('url'))) { - $variables['APP_URL'] = $this->ask('Panel URL', env('APP_URL')); + $variables['APP_URL'] = $this->ask('Panel URL (include http(s)://)', config('app.url')); } else { $variables['APP_URL'] = $this->option('url'); } if (is_null($this->option('timezone'))) { $this->line('The timezone should match one of the supported timezones according to http://php.net/manual/en/timezones.php'); - $variables['APP_TIMEZONE'] = $this->anticipate('Panel Timezone', \DateTimeZone::listIdentifiers(\DateTimeZone::ALL), env('APP_TIMEZONE')); + $variables['APP_TIMEZONE'] = $this->anticipate('Panel Timezone', \DateTimeZone::listIdentifiers(\DateTimeZone::ALL), config('app.timezone')); } else { $variables['APP_TIMEZONE'] = $this->option('timezone'); } + if (is_null($this->option('driver'))) { + $options = [ + 'memcached' => 'Memcache', + 'redis' => 'Redis (recommended)', + 'apc' => 'APC', + 'array' => 'PHP Array', + ]; + $default = (in_array(config('cache.default', 'memcached'), $options)) ? config('cache.default', 'memcached') : 'memcached'; + + $this->line('If you chose redis as your cache driver backend, you *must* have a redis server configured already.'); + $variables['CACHE_DRIVER'] = $this->choice('Which cache driver backend would you like to use?', $options, $default); + } else { + $variables['CACHE_DRIVER'] = $this->option('driver'); + } + + if (is_null($this->option('session-driver'))) { + $options = [ + 'database' => 'MySQL (recommended)', + 'redis' => 'Redis', + 'file' => 'File', + 'cookie' => 'Cookie', + 'apc' => 'APC', + 'array' => 'PHP Array', + ]; + $default = (in_array(config('session.driver', 'database'), $options)) ? config('cache.default', 'database') : 'database'; + + $this->line('If you chose redis as your cache driver backend, you *must* have a redis server configured already.'); + $variables['SESSION_DRIVER'] = $this->choice('Which session driver backend would you like to use?', $options, $default); + } else { + $variables['SESSION_DRIVER'] = $this->option('session-driver'); + } + + if (is_null($this->option('queue-driver'))) { + $options = [ + 'database' => 'Database (recommended)', + 'redis' => 'Redis', + 'sqs' => 'Amazon SQS', + 'sync' => 'Sync', + 'null' => 'None', + ]; + $default = (in_array(config('queue.driver', 'database'), $options)) ? config('queue.driver', 'database') : 'database'; + + $this->line('If you chose redis as your queue driver backend, you *must* have a redis server configured already.'); + $variables['QUEUE_DRIVER'] = $this->choice('Which queue driver backend would you like to use?', $options, $default); + } else { + $variables['QUEUE_DRIVER'] = $this->option('queue-driver'); + } + $bar = $this->output->createProgressBar(count($variables)); - $this->line('Writing new environment configuration to file.'); foreach ($variables as $key => $value) { - $newValue = $key . '=' . $value; + if (str_contains($value, ' ') && ! str_contains($value, '"')) { + $value = '"' . $value . '"'; + } + $newValue = $key . '=' . $value . ' # DO NOT EDIT! set using pterodactyl:env'; if (preg_match_all('/^' . $key . '=(.*)$/m', $envContents) < 1) { $envContents = $envContents . "\n" . $newValue; @@ -150,6 +201,8 @@ class UpdateEnvironment extends Command file_put_contents($file, $envContents); $bar->finish(); - echo "\n"; + + $this->call('config:cache'); + $this->line("\n"); } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c8276b447..bba5bb66e 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -21,6 +21,10 @@ class Kernel extends ConsoleKernel \Pterodactyl\Console\Commands\ClearTasks::class, \Pterodactyl\Console\Commands\ClearServices::class, \Pterodactyl\Console\Commands\UpdateEmailSettings::class, + \Pterodactyl\Console\Commands\CleanServiceBackup::class, + \Pterodactyl\Console\Commands\AddNode::class, + \Pterodactyl\Console\Commands\AddLocation::class, + \Pterodactyl\Console\Commands\RebuildServer::class, ]; /** @@ -33,5 +37,6 @@ class Kernel extends ConsoleKernel { $schedule->command('pterodactyl:tasks')->everyMinute()->withoutOverlapping(); $schedule->command('pterodactyl:tasks:clearlog')->twiceDaily(3, 15); + $schedule->command('pterodactyl:cleanservices')->twiceDaily(1, 13); } } diff --git a/app/Events/Auth/FailedCaptcha.php b/app/Events/Auth/FailedCaptcha.php new file mode 100644 index 000000000..ba741cf6d --- /dev/null +++ b/app/Events/Auth/FailedCaptcha.php @@ -0,0 +1,59 @@ +. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +namespace Pterodactyl\Events\Auth; + +use Illuminate\Queue\SerializesModels; + +class FailedCaptcha +{ + use SerializesModels; + + /** + * The IP that the request originated from. + * + * @var string + */ + public $ip; + + /** + * The domain that was used to try to verify the request with recaptcha api. + * + * @var string + */ + public $domain; + + /** + * Create a new event instance. + * + * @param string $ip + * @param string $domain + * @return void + */ + public function __construct($ip, $domain) + { + $this->ip = $ip; + $this->domain = $domain; + } +} diff --git a/app/Models/ServerVariables.php b/app/Events/Auth/FailedPasswordReset.php similarity index 67% rename from app/Models/ServerVariables.php rename to app/Events/Auth/FailedPasswordReset.php index d5d062489..ec2de3b47 100644 --- a/app/Models/ServerVariables.php +++ b/app/Events/Auth/FailedPasswordReset.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,35 +21,39 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Models; -use Illuminate\Database\Eloquent\Model; +namespace Pterodactyl\Events\Auth; -class ServerVariables extends Model +use Illuminate\Queue\SerializesModels; + +class FailedPasswordReset { + use SerializesModels; /** - * The table associated with the model. + * The IP that the request originated from. * * @var string */ - protected $table = 'server_variables'; + public $ip; /** - * Fields that are not mass assignable. + * The email address that was used when the reset request failed. * - * @var array + * @var string */ - protected $guarded = ['id', 'created_at', 'updated_at']; + public $email; /** - * Cast values to correct type. + * Create a new event instance. * - * @var array + * @param string $ip + * @param string $email + * @return void */ - protected $casts = [ - 'server_id' => 'integer', - 'variable_id' => 'integer', - ]; - + public function __construct($ip, $email) + { + $this->ip = $ip; + $this->email = $email; + } } diff --git a/app/Events/Server/Created.php b/app/Events/Server/Created.php new file mode 100644 index 000000000..2591cd5af --- /dev/null +++ b/app/Events/Server/Created.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Created +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Server + */ + public $server; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Server/Creating.php b/app/Events/Server/Creating.php new file mode 100644 index 000000000..46e4898c1 --- /dev/null +++ b/app/Events/Server/Creating.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Creating +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Server + */ + public $server; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Server/Deleted.php b/app/Events/Server/Deleted.php new file mode 100644 index 000000000..6f8709b85 --- /dev/null +++ b/app/Events/Server/Deleted.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Deleted +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Server + */ + public $server; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Server/Deleting.php b/app/Events/Server/Deleting.php new file mode 100644 index 000000000..3152ed96e --- /dev/null +++ b/app/Events/Server/Deleting.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Deleting +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Server + */ + public $server; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Transformers/ServerTransformer.php b/app/Events/Server/Saved.php old mode 100755 new mode 100644 similarity index 70% rename from app/Transformers/ServerTransformer.php rename to app/Events/Server/Saved.php index 482cf8419..d19659045 --- a/app/Transformers/ServerTransformer.php +++ b/app/Events/Server/Saved.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,22 +21,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Transformers; + +namespace Pterodactyl\Events\Server; use Pterodactyl\Models\Server; -use League\Fractal\TransformerAbstract; +use Illuminate\Queue\SerializesModels; -class ServerTransformer extends TransformerAbstract +class Saved { + use SerializesModels; /** - * Turn this item object into a generic array + * The Eloquent model of the server. * - * @return array + * @var \Pterodactyl\Models\Server */ - public function transform(Server $server) - { - return $server; - } + public $server; + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } } diff --git a/app/Transformers/AllocationTransformer.php b/app/Events/Server/Saving.php similarity index 68% rename from app/Transformers/AllocationTransformer.php rename to app/Events/Server/Saving.php index 2ab9e5b1f..1ae31da32 100644 --- a/app/Transformers/AllocationTransformer.php +++ b/app/Events/Server/Saving.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,22 +21,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Transformers; -use Pterodactyl\Models\Allocation; -use League\Fractal\TransformerAbstract; +namespace Pterodactyl\Events\Server; -class AllocationTransformer extends TransformerAbstract +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Saving { + use SerializesModels; /** - * Turn this item object into a generic array + * The Eloquent model of the server. * - * @return array + * @var \Pterodactyl\Models\Server */ - public function transform(Allocation $allocation) - { - return array_except($allocation, ['created_at', 'updated_at']); - } + public $server; + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } } diff --git a/app/Events/Server/Updated.php b/app/Events/Server/Updated.php new file mode 100644 index 000000000..1284ea504 --- /dev/null +++ b/app/Events/Server/Updated.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Updated +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Server + */ + public $server; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Server/Updating.php b/app/Events/Server/Updating.php new file mode 100644 index 000000000..b2fc255c8 --- /dev/null +++ b/app/Events/Server/Updating.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Updating +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Server + */ + public $server; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Subuser/Created.php b/app/Events/Subuser/Created.php new file mode 100644 index 000000000..9a2d28536 --- /dev/null +++ b/app/Events/Subuser/Created.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Subuser; + +use Pterodactyl\Models\Subuser; +use Illuminate\Queue\SerializesModels; + +class Created +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Subuser + */ + public $subuser; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @return void + */ + public function __construct(Subuser $subuser) + { + $this->subuser = $subuser; + } +} diff --git a/app/Events/Subuser/Creating.php b/app/Events/Subuser/Creating.php new file mode 100644 index 000000000..5083c497c --- /dev/null +++ b/app/Events/Subuser/Creating.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Subuser; + +use Pterodactyl\Models\Subuser; +use Illuminate\Queue\SerializesModels; + +class Creating +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Subuser + */ + public $subuser; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @return void + */ + public function __construct(Subuser $subuser) + { + $this->subuser = $subuser; + } +} diff --git a/app/Events/Subuser/Deleted.php b/app/Events/Subuser/Deleted.php new file mode 100644 index 000000000..1e419ab81 --- /dev/null +++ b/app/Events/Subuser/Deleted.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Subuser; + +use Pterodactyl\Models\Subuser; +use Illuminate\Queue\SerializesModels; + +class Deleted +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Subuser + */ + public $subuser; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @return void + */ + public function __construct(Subuser $subuser) + { + $this->subuser = $subuser; + } +} diff --git a/app/Events/Subuser/Deleting.php b/app/Events/Subuser/Deleting.php new file mode 100644 index 000000000..a06ebe2dc --- /dev/null +++ b/app/Events/Subuser/Deleting.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\Subuser; + +use Pterodactyl\Models\Subuser; +use Illuminate\Queue\SerializesModels; + +class Deleting +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\Subuser + */ + public $subuser; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @return void + */ + public function __construct(Subuser $subuser) + { + $this->subuser = $subuser; + } +} diff --git a/app/Transformers/UserTransformer.php b/app/Events/User/Created.php old mode 100755 new mode 100644 similarity index 71% rename from app/Transformers/UserTransformer.php rename to app/Events/User/Created.php index abd90c34d..4e1fd9e75 --- a/app/Transformers/UserTransformer.php +++ b/app/Events/User/Created.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,22 +21,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Transformers; + +namespace Pterodactyl\Events\User; use Pterodactyl\Models\User; -use League\Fractal\TransformerAbstract; +use Illuminate\Queue\SerializesModels; -class UserTransformer extends TransformerAbstract +class Created { + use SerializesModels; /** - * Turn this item object into a generic array + * The Eloquent model of the server. * - * @return array + * @var \Pterodactyl\Models\User */ - public function transform(User $user) - { - return $user; - } + public $user; + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\User $user + * @return void + */ + public function __construct(User $user) + { + $this->user = $user; + } } diff --git a/app/Events/User/Creating.php b/app/Events/User/Creating.php new file mode 100644 index 000000000..cc544af9e --- /dev/null +++ b/app/Events/User/Creating.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\User; + +use Pterodactyl\Models\User; +use Illuminate\Queue\SerializesModels; + +class Creating +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\User + */ + public $user; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\User $user + * @return void + */ + public function __construct(User $user) + { + $this->user = $user; + } +} diff --git a/app/Transformers/NodeTransformer.php b/app/Events/User/Deleted.php old mode 100755 new mode 100644 similarity index 69% rename from app/Transformers/NodeTransformer.php rename to app/Events/User/Deleted.php index 9b3224c37..a3cc3cffe --- a/app/Transformers/NodeTransformer.php +++ b/app/Events/User/Deleted.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,22 +21,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Transformers; -use Pterodactyl\Models\Node; -use League\Fractal\TransformerAbstract; +namespace Pterodactyl\Events\User; -class NodeTransformer extends TransformerAbstract +use Pterodactyl\Models\User; +use Illuminate\Queue\SerializesModels; + +class Deleted { + use SerializesModels; /** - * Turn this item object into a generic array + * The Eloquent model of the server. * - * @return array + * @var \Pterodactyl\Models\User */ - public function transform(Node $node) - { - return $node; - } + public $user; + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\User $user + * @return void + */ + public function __construct(User $user) + { + $this->user = $user; + } } diff --git a/app/Events/User/Deleting.php b/app/Events/User/Deleting.php new file mode 100644 index 000000000..ad9397e45 --- /dev/null +++ b/app/Events/User/Deleting.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Events\User; + +use Pterodactyl\Models\User; +use Illuminate\Queue\SerializesModels; + +class Deleting +{ + use SerializesModels; + + /** + * The Eloquent model of the server. + * + * @var \Pterodactyl\Models\User + */ + public $user; + + /** + * Create a new event instance. + * + * @param \Pterodactyl\Models\User $user + * @return void + */ + public function __construct(User $user) + { + $this->user = $user; + } +} diff --git a/app/Exceptions/AccountNotFoundException.php b/app/Exceptions/AccountNotFoundException.php index 166031644..b35bd2fc0 100644 --- a/app/Exceptions/AccountNotFoundException.php +++ b/app/Exceptions/AccountNotFoundException.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,9 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Exceptions; class AccountNotFoundException extends \Exception { - + // } diff --git a/app/Http/Controllers/API/BaseController.php b/app/Exceptions/AutoDeploymentException.php old mode 100755 new mode 100644 similarity index 82% rename from app/Http/Controllers/API/BaseController.php rename to app/Exceptions/AutoDeploymentException.php index 7d897fe55..109fe1096 --- a/app/Http/Controllers/API/BaseController.php +++ b/app/Exceptions/AutoDeploymentException.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,12 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Http\Controllers\API; -use Dingo\Api\Routing\Helpers; -use Illuminate\Routing\Controller; +namespace Pterodactyl\Exceptions; -class BaseController extends Controller +class AutoDeploymentException extends \Exception { - use Helpers; + // } diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index ae06f983a..f3f8fd719 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,28 +21,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Exceptions; use Log; class DisplayException extends \Exception { - - private $_logging = null; - + /** + * Exception constructor. + * + * @param string $message + * @param mixed $log + * @return void + */ public function __construct($message, $log = null) { - $this->_logging = $log; - if ($this->_logging !== null) { + if (! is_null($log)) { Log::error($log); } parent::__construct($message); } - - public function getLogging() - { - return $this->_logging; - } - } diff --git a/app/Exceptions/DisplayValidationException.php b/app/Exceptions/DisplayValidationException.php index e8d319f4c..3d8a4fda2 100644 --- a/app/Exceptions/DisplayValidationException.php +++ b/app/Exceptions/DisplayValidationException.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Exceptions; class DisplayValidationException extends \Exception diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 2230c771d..a801cdceb 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -3,12 +3,7 @@ namespace Pterodactyl\Exceptions; use Log; - use Exception; -use DisplayException; -use DisplayValidationException; -use AccountNotFoundException; - use Illuminate\Auth\AuthenticationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; @@ -33,7 +28,7 @@ class Handler extends ExceptionHandler * * This is a great spot to send exceptions to Sentry, Bugsnag, etc. * - * @param \Exception $e + * @param \Exception $exception * @return void */ public function report(Exception $exception) @@ -45,18 +40,27 @@ class Handler extends ExceptionHandler * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request - * @param \Exception $e + * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { - if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('remote/*')) { - $response = response()->json([ - 'error' => ($exception instanceof DisplayException) ? $exception->getMessage() : 'An unhandled error occured while attempting to process this request.' - ], 500); + if ($request->expectsJson() || $request->isJson() || $request->is(...config('pterodactyl.json_routes'))) { + $exception = $this->prepareException($exception); - // parent::render() will log it, we are bypassing it in this case. - Log::error($exception); + if (config('app.debug') || $this->isHttpException($exception)) { + $displayError = $exception->getMessage(); + } else { + $displayError = 'An unhandled exception was encountered with this request.'; + } + + $response = response()->json([ + 'error' => $displayError, + 'http_code' => (! $this->isHttpException($exception)) ?: $exception->getStatusCode(), + 'trace' => (! config('app.debug')) ? null : class_basename($exception) . ' in ' . $exception->getFile() . ' on line ' . $exception->getLine(), + ], ($this->isHttpException($exception)) ? $exception->getStatusCode() : 500, [], JSON_UNESCAPED_SLASHES); + + parent::report($exception); } return (isset($response)) ? $response : parent::render($request, $exception); @@ -65,7 +69,7 @@ class Handler extends ExceptionHandler /** * Convert an authentication exception into an unauthenticated response. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @param \Illuminate\Auth\AuthenticationException $exception * @return \Illuminate\Http\Response */ @@ -74,7 +78,7 @@ class Handler extends ExceptionHandler if ($request->expectsJson()) { return response()->json(['error' => 'Unauthenticated.'], 401); } - return redirect()->guest('/auth/login'); - } + return redirect()->guest(route('auth.login')); + } } diff --git a/app/Extensions/PhraseAppTranslator.php b/app/Extensions/PhraseAppTranslator.php new file mode 100644 index 000000000..f79cf2820 --- /dev/null +++ b/app/Extensions/PhraseAppTranslator.php @@ -0,0 +1,46 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Extensions; + +use Illuminate\Translation\Translator as LaravelTranslator; + +class PhraseAppTranslator extends LaravelTranslator +{ + /** + * Get the translation for the given key. + * + * @param string $key + * @param array $replace + * @param string|null $locale + * @param bool $fallback + * @return string + */ + public function get($key, array $replace = [], $locale = null, $fallback = true) + { + $key = substr($key, strpos($key, '.') + 1); + + return "{{__phrase_${key}__}}"; + } +} diff --git a/app/Events/ServerDeleted.php b/app/Facades/Version.php similarity index 77% rename from app/Events/ServerDeleted.php rename to app/Facades/Version.php index 4451b01bc..e9475d2a6 100644 --- a/app/Events/ServerDeleted.php +++ b/app/Facades/Version.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,24 +21,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Events; -use Illuminate\Queue\SerializesModels; +namespace Pterodactyl\Facades; -class ServerDeleted +use Illuminate\Support\Facades\Facade; + +class Version extends Facade { - use SerializesModels; - - public $server; - /** - * Create a new event instance. + * Returns the facade accessor class. * - * @return void + * @return strig */ - public function __construct($id) + protected static function getFacadeAccessor() { - $this->server = $id; + return '\Pterodactyl\Services\VersionService'; } - } diff --git a/app/Http/Controllers/API/Admin/LocationController.php b/app/Http/Controllers/API/Admin/LocationController.php new file mode 100644 index 000000000..41405e91e --- /dev/null +++ b/app/Http/Controllers/API/Admin/LocationController.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\API\Admin; + +use Fractal; +use Illuminate\Http\Request; +use Pterodactyl\Models\Location; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Transformers\Admin\LocationTransformer; + +class LocationController extends Controller +{ + /** + * Controller to handle returning all locations on the system. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function index(Request $request) + { + $this->authorize('location-list', $request->apiKey()); + + return Fractal::create() + ->collection(Location::all()) + ->transformWith(new LocationTransformer($request)) + ->withResourceName('location') + ->toArray(); + } +} diff --git a/app/Http/Controllers/API/Admin/NodeController.php b/app/Http/Controllers/API/Admin/NodeController.php new file mode 100644 index 000000000..74784fdb6 --- /dev/null +++ b/app/Http/Controllers/API/Admin/NodeController.php @@ -0,0 +1,174 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\API\Admin; + +use Log; +use Fractal; +use Illuminate\Http\Request; +use Pterodactyl\Models\Node; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\NodeRepository; +use Pterodactyl\Transformers\Admin\NodeTransformer; +use Pterodactyl\Exceptions\DisplayValidationException; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; + +class NodeController extends Controller +{ + /** + * Controller to handle returning all nodes on the system. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function index(Request $request) + { + $this->authorize('node-list', $request->apiKey()); + + $nodes = Node::paginate(config('pterodactyl.paginate.api.nodes')); + $fractal = Fractal::create()->collection($nodes) + ->transformWith(new NodeTransformer($request)) + ->withResourceName('user') + ->paginateWith(new IlluminatePaginatorAdapter($nodes)); + + if (config('pterodactyl.api.include_on_list') && $request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->toArray(); + } + + /** + * Display information about a single node on the system. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return array + */ + public function view(Request $request, $id) + { + $this->authorize('node-view', $request->apiKey()); + + $fractal = Fractal::create()->item(Node::findOrFail($id)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->transformWith(new NodeTransformer($request)) + ->withResourceName('node') + ->toArray(); + } + + /** + * Display information about a single node on the system. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\JsonResponse + */ + public function viewConfig(Request $request, $id) + { + $this->authorize('node-view-config', $request->apiKey()); + + $node = Node::findOrFail($id); + + return response()->json(json_decode($node->getConfigurationAsJson())); + } + + /** + * Create a new node on the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse|array + */ + public function store(Request $request) + { + $this->authorize('node-create', $request->apiKey()); + + $repo = new NodeRepository; + try { + $node = $repo->create(array_merge( + $request->only([ + 'public', 'disk_overallocate', 'memory_overallocate', + ]), + $request->intersect([ + 'name', 'location_id', 'fqdn', + 'scheme', 'memory', 'disk', + 'daemonBase', 'daemonSFTP', 'daemonListen', + ]) + )); + + $fractal = Fractal::create()->item($node)->transformWith(new NodeTransformer($request)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->withResourceName('node')->toArray(); + } catch (DisplayValidationException $ex) { + return response()->json([ + 'error' => json_decode($ex->getMessage()), + ], 400); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to create this node. Please try again.', + ], 500); + } + } + + /** + * Delete a node from the system. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function delete(Request $request, $id) + { + $this->authorize('node-delete', $request->apiKey()); + + $repo = new NodeRepository; + try { + $repo->delete($id); + + return response('', 204); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to delete this node. Please try again.', + ], 500); + } + } +} diff --git a/app/Http/Controllers/API/Admin/ServerController.php b/app/Http/Controllers/API/Admin/ServerController.php new file mode 100644 index 000000000..28cb40641 --- /dev/null +++ b/app/Http/Controllers/API/Admin/ServerController.php @@ -0,0 +1,429 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\API\Admin; + +use Log; +use Fractal; +use Illuminate\Http\Request; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\TransferException; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\ServerRepository; +use Pterodactyl\Transformers\Admin\ServerTransformer; +use Pterodactyl\Exceptions\DisplayValidationException; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; + +class ServerController extends Controller +{ + /** + * Controller to handle returning all servers on the system. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function index(Request $request) + { + $this->authorize('server-list', $request->apiKey()); + + $servers = Server::paginate(config('pterodactyl.paginate.api.servers')); + $fractal = Fractal::create()->collection($servers) + ->transformWith(new ServerTransformer($request)) + ->withResourceName('user') + ->paginateWith(new IlluminatePaginatorAdapter($servers)); + + if (config('pterodactyl.api.include_on_list') && $request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->toArray(); + } + + /** + * Controller to handle returning information on a single server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return array + */ + public function view(Request $request, $id) + { + $this->authorize('server-view', $request->apiKey()); + + $server = Server::findOrFail($id); + $fractal = Fractal::create()->item($server); + + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->transformWith(new ServerTransformer($request)) + ->withResourceName('server') + ->toArray(); + } + + /** + * Create a new server on the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse|array + */ + public function store(Request $request) + { + $this->authorize('server-create', $request->apiKey()); + + $repo = new ServerRepository; + try { + $server = $repo->create($request->all()); + + $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->withResourceName('server')->toArray(); + } catch (DisplayValidationException $ex) { + return response()->json([ + 'error' => json_decode($ex->getMessage()), + ], 400); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (TransferException $ex) { + Log::warning($ex); + + return response()->json([ + 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', + ], 504); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to add this server. Please try again.', + ], 500); + } + } + + /** + * Delete a server from the system. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function delete(Request $request, $id) + { + $this->authorize('server-delete', $request->apiKey()); + + $repo = new ServerRepository; + try { + $repo->delete($id, $request->has('force_delete')); + + return response('', 204); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (TransferException $ex) { + Log::warning($ex); + + return response()->json([ + 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', + ], 504); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to add this server. Please try again.', + ], 500); + } + } + + /** + * Update the details for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\JsonResponse|array + */ + public function details(Request $request, $id) + { + $this->authorize('server-edit-details', $request->apiKey()); + + $repo = new ServerRepository; + try { + $server = $repo->updateDetails($id, $request->intersect([ + 'owner_id', 'name', 'description', 'reset_token', + ])); + + $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->withResourceName('server')->toArray(); + } catch (DisplayValidationException $ex) { + return response()->json([ + 'error' => json_decode($ex->getMessage()), + ], 400); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to modify this server. Please try again.', + ], 500); + } + } + + /** + * Set the new docker container for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse|array + */ + public function container(Request $request, $id) + { + $this->authorize('server-edit-container', $request->apiKey()); + + $repo = new ServerRepository; + try { + $server = $repo->updateContainer($id, $request->intersect('docker_image')); + + $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->withResourceName('server')->toArray(); + } catch (DisplayValidationException $ex) { + return response()->json([ + 'error' => json_decode($ex->getMessage()), + ], 400); + } catch (TransferException $ex) { + Log::warning($ex); + + return response()->json([ + 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', + ], 504); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to modify this server container. Please try again.', + ], 500); + } + } + + /** + * Toggles the install status for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function install(Request $request, $id) + { + $this->authorize('server-install', $request->apiKey()); + + $repo = new ServerRepository; + try { + $repo->toggleInstall($id); + + return response('', 204); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to toggle the install status for this server. Please try again.', + ], 500); + } + } + + /** + * Setup a server to have a container rebuild. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function rebuild(Request $request, $id) + { + $this->authorize('server-rebuild', $request->apiKey()); + $server = Server::with('node')->findOrFail($id); + + try { + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('POST', '/server/rebuild'); + + return response('', 204); + } catch (TransferException $ex) { + Log::warning($ex); + + return response()->json([ + 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', + ], 504); + } + } + + /** + * Manage the suspension status for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function suspend(Request $request, $id) + { + $this->authorize('server-suspend', $request->apiKey()); + + $repo = new ServerRepository; + $action = $request->input('action'); + if (! in_array($action, ['suspend', 'unsuspend'])) { + return response()->json([ + 'error' => 'The action provided was invalid. Action should be one of: suspend, unsuspend.', + ], 400); + } + + try { + $repo->toggleAccess($id, ($action === 'unsuspend')); + + return response('', 204); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (TransferException $ex) { + Log::warning($ex); + + return response()->json([ + 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', + ], 504); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to ' . $action . ' this server. Please try again.', + ], 500); + } + } + + /** + * Update the build configuration for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\JsonResponse|array + */ + public function build(Request $request, $id) + { + $this->authorize('server-edit-build', $request->apiKey()); + + $repo = new ServerRepository; + try { + $server = $repo->changeBuild($id, $request->intersect([ + 'allocation_id', 'add_allocations', 'remove_allocations', + 'memory', 'swap', 'io', 'cpu', + ])); + + $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->withResourceName('server')->toArray(); + } catch (DisplayValidationException $ex) { + return response()->json([ + 'error' => json_decode($ex->getMessage()), + ], 400); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (TransferException $ex) { + Log::warning($ex); + + return response()->json([ + 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', + ], 504); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to modify the build settings for this server. Please try again.', + ], 500); + } + } + + /** + * Update the startup command as well as variables. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function startup(Request $request, $id) + { + $this->authorize('server-edit-startup', $request->apiKey()); + + $repo = new ServerRepository; + try { + $repo->updateStartup($id, $request->all(), true); + + return response('', 204); + } catch (DisplayValidationException $ex) { + return response()->json([ + 'error' => json_decode($ex->getMessage()), + ], 400); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (TransferException $ex) { + Log::warning($ex); + + return response()->json([ + 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', + ], 504); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to modify the startup settings for this server. Please try again.', + ], 500); + } + } +} diff --git a/app/Http/Controllers/API/Admin/ServiceController.php b/app/Http/Controllers/API/Admin/ServiceController.php new file mode 100644 index 000000000..fbe911f14 --- /dev/null +++ b/app/Http/Controllers/API/Admin/ServiceController.php @@ -0,0 +1,74 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\API\Admin; + +use Fractal; +use Illuminate\Http\Request; +use Pterodactyl\Models\Service; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Transformers\Admin\ServiceTransformer; + +class ServiceController extends Controller +{ + /** + * Controller to handle returning all locations on the system. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function index(Request $request) + { + $this->authorize('service-list', $request->apiKey()); + + return Fractal::create() + ->collection(Service::all()) + ->transformWith(new ServiceTransformer($request)) + ->withResourceName('service') + ->toArray(); + } + + /** + * Controller to handle returning information on a single server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return array + */ + public function view(Request $request, $id) + { + $this->authorize('service-view', $request->apiKey()); + + $service = Service::findOrFail($id); + $fractal = Fractal::create()->item($service); + + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->transformWith(new ServiceTransformer($request)) + ->withResourceName('service') + ->toArray(); + } +} diff --git a/app/Http/Controllers/API/Admin/UserController.php b/app/Http/Controllers/API/Admin/UserController.php new file mode 100644 index 000000000..c94fe8090 --- /dev/null +++ b/app/Http/Controllers/API/Admin/UserController.php @@ -0,0 +1,185 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\API\Admin; + +use Log; +use Fractal; +use Illuminate\Http\Request; +use Pterodactyl\Models\User; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\UserRepository; +use Pterodactyl\Transformers\Admin\UserTransformer; +use Pterodactyl\Exceptions\DisplayValidationException; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; + +class UserController extends Controller +{ + /** + * Controller to handle returning all users on the system. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function index(Request $request) + { + $this->authorize('user-list', $request->apiKey()); + + $users = User::paginate(config('pterodactyl.paginate.api.users')); + $fractal = Fractal::create()->collection($users) + ->transformWith(new UserTransformer($request)) + ->withResourceName('user') + ->paginateWith(new IlluminatePaginatorAdapter($users)); + + if (config('pterodactyl.api.include_on_list') && $request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->toArray(); + } + + /** + * Display information about a single user on the system. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return array + */ + public function view(Request $request, $id) + { + $this->authorize('user-view', $request->apiKey()); + + $fractal = Fractal::create()->item(User::findOrFail($id)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->transformWith(new UserTransformer($request)) + ->withResourceName('user') + ->toArray(); + } + + /** + * Create a new user on the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse|array + */ + public function store(Request $request) + { + $this->authorize('user-create', $request->apiKey()); + + $repo = new UserRepository; + try { + $user = $repo->create($request->only([ + 'custom_id', 'email', 'password', 'name_first', + 'name_last', 'username', 'root_admin', + ])); + + $fractal = Fractal::create()->item($user)->transformWith(new UserTransformer($request)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->withResourceName('user')->toArray(); + } catch (DisplayValidationException $ex) { + return response()->json([ + 'error' => json_decode($ex->getMessage()), + ], 400); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to create this user. Please try again.', + ], 500); + } + } + + /** + * Update a user. + * + * @param \Illuminate\Http\Request $request + * @param int $user + * @return \Illuminate\Http\RedirectResponse + */ + public function update(Request $request, $user) + { + $this->authorize('user-edit', $request->apiKey()); + + $repo = new UserRepository; + try { + $user = $repo->update($user, $request->intersect([ + 'email', 'password', 'name_first', + 'name_last', 'username', 'root_admin', + ])); + + $fractal = Fractal::create()->item($user)->transformWith(new UserTransformer($request)); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->withResourceName('user')->toArray(); + } catch (DisplayValidationException $ex) { + return response()->json([ + 'error' => json_decode($ex->getMessage()), + ], 400); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to update this user. Please try again.', + ], 500); + } + } + + /** + * Delete a user from the system. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function delete(Request $request, $id) + { + $this->authorize('user-delete', $request->apiKey()); + + $repo = new UserRepository; + try { + $repo->delete($id); + + return response('', 204); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 400); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An unhandled exception occured while attemping to delete this user. Please try again.', + ], 500); + } + } +} diff --git a/app/Http/Controllers/API/NodeController.php b/app/Http/Controllers/API/NodeController.php deleted file mode 100755 index a0da14af5..000000000 --- a/app/Http/Controllers/API/NodeController.php +++ /dev/null @@ -1,253 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Controllers\API; - -use Illuminate\Http\Request; - -use Pterodactyl\Models; -use Pterodactyl\Transformers\NodeTransformer; -use Pterodactyl\Transformers\AllocationTransformer; -use Pterodactyl\Repositories\NodeRepository; - -use Pterodactyl\Exceptions\DisplayValidationException; -use Pterodactyl\Exceptions\DisplayException; -use Dingo\Api\Exception\ResourceException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; - -/** - * @Resource("Servers") - */ -class NodeController extends BaseController -{ - - public function __construct() - { - // - } - - /** - * List All Nodes - * - * Lists all nodes currently on the system. - * - * @Get("/nodes/{?page}") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("page", type="integer", description="The page of results to view.", default=1) - * }) - * @Response(200) - */ - public function list(Request $request) - { - return Models\Node::all()->toArray(); - } - - /** - * Create a New Node - * - * @Post("/nodes") - * @Versions({"v1"}) - * @Transaction({ - * @Request({ - * 'name' => 'My API Node', - * 'location' => 1, - * 'public' => 1, - * 'fqdn' => 'daemon.wuzzle.woo', - * 'scheme' => 'https', - * 'memory' => 10240, - * 'memory_overallocate' => 100, - * 'disk' => 204800, - * 'disk_overallocate' => -1, - * 'daemonBase' => '/srv/daemon-data', - * 'daemonSFTP' => 2022, - * 'daemonListen' => 8080 - * }, headers={"Authorization": "Bearer "}), - * @Response(200), - * @Response(422, body={ - * "message": "A validation error occured.", - * "errors": {}, - * "status_code": 422 - * }), - * @Response(503, body={ - * "message": "There was an error while attempting to add this node to the system.", - * "status_code": 503 - * }) - * }) - */ - public function create(Request $request) - { - try { - $node = new NodeRepository; - $new = $node->create($request->all()); - return [ 'id' => $new ]; - } catch (DisplayValidationException $ex) { - throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $e) { - throw new BadRequestHttpException('There was an error while attempting to add this node to the system.'); - } - } - - /** - * List Specific Node - * - * Lists specific fields about a server or all fields pertaining to that node. - * - * @Get("/nodes/{id}/{?fields}") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the node to get information on."), - * @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.") - * }) - * @Response(200) - */ - public function view(Request $request, $id, $fields = null) - { - $node = Models\Node::where('id', $id); - - if (!is_null($request->input('fields'))) { - foreach(explode(',', $request->input('fields')) as $field) { - if (!empty($field)) { - $node->addSelect($field); - } - } - } - - try { - if (!$node->first()) { - throw new NotFoundHttpException('No node by that ID was found.'); - } - - return [ - 'node' => $node->first(), - 'allocations' => [ - 'assigned' => Models\Allocation::where('node', $id)->whereNotNull('assigned_to')->get(), - 'unassigned' => Models\Allocation::where('node', $id)->whereNull('assigned_to')->get() - ] - ]; - } catch (NotFoundHttpException $ex) { - throw $ex; - } catch (\Exception $ex) { - throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); - } - } - - public function config(Request $request, $id) - { - if (!$request->secure()) { - throw new BadRequestHttpException('This API route can only be accessed using a secure connection.'); - } - - $node = Models\Node::where('id', $id)->first(); - if (!$node) { - throw new NotFoundHttpException('No node by that ID was found.'); - } - - return [ - 'web' => [ - 'listen' => $node->daemonListen, - 'ssl' => [ - 'enabled' => ($node->scheme === 'https'), - 'certificate' => '/etc/certs/' . $node->fqdn . '/fullchain.pem', - 'key' => '/etc/certs/' . $node->fqdn . '/privkey.pem' - ] - ], - 'docker' => [ - 'socket' => '/var/run/docker.sock', - 'autoupdate_images' => true - ], - 'sftp' => [ - 'path' => $node->daemonBase, - 'port' => (int) $node->daemonSFTP, - 'container' => '0x0000' - ], - 'logger' => [ - 'path' => 'logs/', - 'src' => false, - 'level' => 'info', - 'period' => '1d', - 'count' => 3 - ], - 'remote' => [ - 'download' => route('remote.download'), - 'installed' => route('remote.install') - ], - 'uploads' => [ - 'maximumSize' => 100000000 - ], - 'keys' => [ - $node->daemonSecret - ], - 'query' => [ - 'kill_on_fail' => true, - 'fail_limit' => 3 - ] - ]; - } - - /** - * List all Node Allocations - * - * Returns a listing of all allocations for every node. - * - * @Get("/nodes/allocations") - * @Versions({"v1"}) - * @Response(200) - */ - public function allocations(Request $request) - { - $allocations = Models\Allocation::all(); - if ($allocations->count() < 1) { - throw new NotFoundHttpException('No allocations have been created.'); - } - return $allocations; - } - - /** - * Delete Node - * - * @Delete("/nodes/{id}") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the node."), - * }) - * @Response(204) - */ - public function delete(Request $request, $id) - { - try { - $node = new NodeRepository; - $node->delete($id); - return $this->response->noContent(); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch(\Exception $e) { - throw new ServiceUnavailableHttpException('An error occured while attempting to delete this node.'); - } - } - -} diff --git a/app/Http/Controllers/API/ServerController.php b/app/Http/Controllers/API/ServerController.php deleted file mode 100755 index 2b9f0986c..000000000 --- a/app/Http/Controllers/API/ServerController.php +++ /dev/null @@ -1,308 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Controllers\API; - -use Illuminate\Http\Request; - -use Log; -use Pterodactyl\Models; -use Pterodactyl\Transformers\ServerTransformer; -use Pterodactyl\Repositories\ServerRepository; - -use Pterodactyl\Exceptions\DisplayValidationException; -use Pterodactyl\Exceptions\DisplayException; -use Dingo\Api\Exception\ResourceException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; - -/** - * @Resource("Servers") - */ -class ServerController extends BaseController -{ - - public function __construct() - { - // - } - - /** - * List All Servers - * - * Lists all servers currently on the system. - * - * @Get("/servers/{?page}") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("page", type="integer", description="The page of results to view.", default=1) - * }) - * @Response(200) - */ - public function list(Request $request) - { - return Models\Server::all()->toArray(); - } - - /** - * Create Server - * - * @Post("/servers") - * @Versions({"v1"}) - * @Response(201) - */ - public function create(Request $request) - { - try { - $server = new ServerRepository; - $new = $server->create($request->all()); - return [ 'id' => $new ]; - } catch (DisplayValidationException $ex) { - throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $ex) { - Log::error($ex); - throw new BadRequestHttpException('There was an error while attempting to add this server to the system.'); - } - } - - /** - * List Specific Server - * - * Lists specific fields about a server or all fields pertaining to that server. - * - * @Get("/servers/{id}{?fields}") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the server to get information on."), - * @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.") - * }) - * @Response(200) - */ - public function view(Request $request, $id) - { - $query = Models\Server::where('id', $id); - - if (!is_null($request->input('fields'))) { - foreach(explode(',', $request->input('fields')) as $field) { - if (!empty($field)) { - $query->addSelect($field); - } - } - } - - try { - if (!$query->first()) { - throw new NotFoundHttpException('No server by that ID was found.'); - } - - // Requested Daemon Stats - $server = $query->first(); - if ($request->input('daemon') === 'true') { - $node = Models\Node::findOrFail($server->node); - $client = Models\Node::guzzleRequest($node->id); - - $response = $client->request('GET', '/servers', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret - ] - ]); - - // Only return the daemon token if the request is using HTTPS - if ($request->secure()) { - $server->daemon_token = $server->daemonSecret; - } - $server->daemon = json_decode($response->getBody())->{$server->uuid}; - - return $server->toArray(); - } - - return $server->toArray(); - - } catch (NotFoundHttpException $ex) { - throw $ex; - } catch (\GuzzleHttp\Exception\TransferException $ex) { - // Couldn't hit the daemon, return what we have though. - $server->daemon = [ - 'error' => 'There was an error encountered while attempting to connect to the remote daemon.' - ]; - return $server->toArray(); - } catch (\Exception $ex) { - throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); - } - } - - /** - * Update Server configuration - * - * Updates display information on panel. - * - * @Patch("/servers/{id}/config") - * @Versions({"v1"}) - * @Transaction({ - * @Request({ - * "owner": "new@email.com", - * "name": "New Name", - * "reset_token": true - * }, headers={"Authorization": "Bearer "}), - * @Response(200, body={"name": "New Name"}), - * @Response(422) - * }) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the server to modify.") - * }) - */ - public function config(Request $request, $id) - { - try { - $server = new ServerRepository; - $server->updateDetails($id, $request->all()); - return Models\Server::findOrFail($id); - } catch (DisplayValidationException $ex) { - throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $ex) { - throw new ServiceUnavailableHttpException('Unable to update server on system due to an error.'); - } - } - - /** - * Update Server Build Configuration - * - * Updates server build information on panel and on node. - * - * @Patch("/servers/{id}/build") - * @Versions({"v1"}) - * @Transaction({ - * @Request({ - * "default": "192.168.0.1:25565", - * "add_additional": [ - * "192.168.0.1:25566", - * "192.168.0.1:25567", - * "192.168.0.1:25568" - * ], - * "remove_additional": [], - * "memory": 1024, - * "swap": 0, - * "io": 500, - * "cpu": 0, - * "disk": 1024 - * }, headers={"Authorization": "Bearer "}), - * @Response(200, body={"name": "New Name"}), - * @Response(422) - * }) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the server to modify.") - * }) - */ - public function build(Request $request, $id) - { - try { - throw new BadRequestHttpException('There was an error while attempting to add this node to the system.'); - - $server = new ServerRepository; - $server->changeBuild($id, $request->all()); - return Models\Server::findOrFail($id); - } catch (DisplayValidationException $ex) { - throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $ex) { - throw new ServiceUnavailableHttpException('Unable to update server on system due to an error.'); - } - } - - /** - * Suspend Server - * - * @Post("/servers/{id}/suspend") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the server."), - * }) - * @Response(204) - */ - public function suspend(Request $request, $id) - { - try { - $server = new ServerRepository; - $server->suspend($id); - return $this->response->noContent(); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $ex) { - throw new ServiceUnavailableHttpException('An error occured while attempting to suspend this server instance.'); - } - } - - /** - * Unsuspend Server - * - * @Post("/servers/{id}/unsuspend") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the server."), - * }) - * @Response(204) - */ - public function unsuspend(Request $request, $id) - { - try { - $server = new ServerRepository; - $server->unsuspend($id); - return $this->response->noContent(); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $ex) { - throw new ServiceUnavailableHttpException('An error occured while attempting to unsuspend this server instance.'); - } - } - - /** - * Delete Server - * - * @Delete("/servers/{id}/{force}") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the server."), - * @Parameter("force", type="string", required=false, description="Use 'force' if the server should be removed regardless of daemon response."), - * }) - * @Response(204) - */ - public function delete(Request $request, $id, $force = null) - { - try { - $server = new ServerRepository; - $server->deleteServer($id, $force); - return $this->response->noContent(); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch(\Exception $e) { - throw new ServiceUnavailableHttpException('An error occured while attempting to delete this server.'); - } - } - -} diff --git a/app/Http/Controllers/API/ServiceController.php b/app/Http/Controllers/API/ServiceController.php deleted file mode 100755 index 5a60990e6..000000000 --- a/app/Http/Controllers/API/ServiceController.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Controllers\API; - -use Illuminate\Http\Request; - -use Pterodactyl\Models; -use Pterodactyl\Transformers\ServiceTransformer; - -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -/** - * @Resource("Services") - */ -class ServiceController extends BaseController -{ - - public function __construct() - { - // - } - - public function list(Request $request) - { - return Models\Service::all()->toArray(); - } - - public function view(Request $request, $id) - { - $service = Models\Service::find($id); - if (!$service) { - throw new NotFoundHttpException('No service by that ID was found.'); - } - - $options = Models\ServiceOptions::select('id', 'name', 'description', 'tag', 'docker_image')->where('parent_service', $service->id)->get(); - foreach($options as &$opt) { - $opt->variables = Models\ServiceVariables::where('option_id', $opt->id)->get(); - } - - return [ - 'service' => $service, - 'options' => $options - ]; - - } - -} diff --git a/app/Http/Controllers/API/User/CoreController.php b/app/Http/Controllers/API/User/CoreController.php new file mode 100644 index 000000000..5c1d07406 --- /dev/null +++ b/app/Http/Controllers/API/User/CoreController.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\API\User; + +use Fractal; +use Illuminate\Http\Request; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Transformers\User\OverviewTransformer; + +class CoreController extends Controller +{ + /** + * Controller to handle base user request for all of their servers. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function index(Request $request) + { + $this->authorize('user.server-list', $request->apiKey()); + + $servers = $request->user()->access('service', 'node', 'allocation', 'option')->get(); + + return Fractal::collection($servers) + ->transformWith(new OverviewTransformer) + ->withResourceName('server') + ->toArray(); + } +} diff --git a/app/Http/Controllers/API/User/InfoController.php b/app/Http/Controllers/API/User/InfoController.php deleted file mode 100644 index 2e2e3a03b..000000000 --- a/app/Http/Controllers/API/User/InfoController.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Controllers\API\User; - -use Auth; -use Dingo; -use Pterodactyl\Models; -use Illuminate\Http\Request; - -use Pterodactyl\Http\Controllers\API\BaseController; - -class InfoController extends BaseController -{ - public function me(Request $request) - { - $servers = Models\Server::getUserServers(); - $response = []; - - foreach($servers as &$server) { - $response = array_merge($response, [[ - 'id' => $server->uuidShort, - 'uuid' => $server->uuid, - 'name' => $server->name, - 'node' => $server->nodeName, - 'ip' => [ - 'set' => $server->ip, - 'alias' => $server->ip_alias - ], - 'port' => $server->port, - 'service' => $server->a_serviceName, - 'option' => $server->a_serviceOptionName - ]]); - } - - return $response; - } -} diff --git a/app/Http/Controllers/API/User/ServerController.php b/app/Http/Controllers/API/User/ServerController.php index 613f90582..904935186 100644 --- a/app/Http/Controllers/API/User/ServerController.php +++ b/app/Http/Controllers/API/User/ServerController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,97 +21,79 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\API\User; -use Auth; -use Log; -use Pterodactyl\Models; +use Fractal; use Illuminate\Http\Request; +use Pterodactyl\Models\Server; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\Daemon\PowerRepository; +use Pterodactyl\Transformers\User\ServerTransformer; +use Pterodactyl\Repositories\Daemon\CommandRepository; -use Pterodactyl\Http\Controllers\API\BaseController; - -class ServerController extends BaseController +class ServerController extends Controller { - - public function info(Request $request, $uuid) + /** + * Controller to handle base request for individual server information. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return array + */ + public function index(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); - $node = Models\Node::findOrFail($server->node); - $client = Models\Node::guzzleRequest($node->id); + $this->authorize('user.server-view', $request->apiKey()); - try { - $response = $client->request('GET', '/server', [ - 'headers' => [ - 'X-Access-Token' => $server->daemonSecret, - 'X-Access-Server' => $server->uuid - ] - ]); + $server = Server::byUuid($uuid); + $fractal = Fractal::create()->item($server); - $json = json_decode($response->getBody()); - $daemon = [ - 'status' => $json->status, - 'stats' => $json->proc, - 'query' => $json->query - ]; - } catch (\Exception $ex) { - $daemon = [ - 'error' => 'An error was encountered while trying to connect to the daemon to collece information. It might be offline.' - ]; - Log::error($ex); + if ($request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); } - $allocations = Models\Allocation::select('id', 'ip', 'port', 'ip_alias as alias')->where('assigned_to', $server->id)->get(); - foreach($allocations as &$allocation) { - $allocation->default = ($allocation->id === $server->allocation); - unset($allocation->id); - } - return [ - 'uuidShort' => $server->uuidShort, - 'uuid' => $server->uuid, - 'name' => $server->name, - 'node' => $node->name, - 'limits' => [ - 'memory' => $server->memory, - 'swap' => $server->swap, - 'disk' => $server->disk, - 'io' => $server->io, - 'cpu' => $server->cpu, - 'oom_disabled' => (bool) $server->oom_disabled - ], - 'allocations' => $allocations, - 'sftp' => [ - 'username' => (Auth::user()->can('view-sftp', $server)) ? $server->username : null - ], - 'daemon' => [ - 'token' => ($request->secure()) ? $server->daemonSecret : false, - 'response' => $daemon - ] - ]; + return $fractal->transformWith(new ServerTransformer) + ->withResourceName('server') + ->toArray(); } + /** + * Controller to handle request for server power toggle. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\Response + */ public function power(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); + $this->authorize('user.server-power', $request->apiKey()); - Auth::user()->can('power-' . $request->input('action'), $server); + $server = Server::byUuid($uuid); + $request->user()->can('power-' . $request->input('action'), $server); - $res = $client->request('PUT', '/server/power', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->daemonSecret - ], - 'exceptions' => false, - 'json' => [ - 'action' => $request->input('action') - ] - ]); + $repo = new PowerRepository($server, $request->user()); + $repo->do($request->input('action')); - if ($res->getStatusCode() !== 204) { - return $this->response->error(json_decode($res->getBody())->error, $res->getStatusCode()); - } + return response('', 204)->header('Content-Type', 'application/json'); + } - return $this->response->noContent(); + /** + * Controller to handle base request for individual server information. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\Response + */ + public function command(Request $request, $uuid) + { + $this->authorize('user.server-command', $request->apiKey()); + + $server = Server::byUuid($uuid); + $request->user()->can('send-command', $server); + + $repo = new CommandRepository($server, $request->user()); + $repo->send($request->input('command')); + + return response('', 204)->header('Content-Type', 'application/json'); } } diff --git a/app/Http/Controllers/API/UserController.php b/app/Http/Controllers/API/UserController.php deleted file mode 100755 index 564f69215..000000000 --- a/app/Http/Controllers/API/UserController.php +++ /dev/null @@ -1,202 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Controllers\API; - -use Illuminate\Http\Request; - -use Dingo\Api\Exception\ResourceException; - -use Pterodactyl\Models; -use Pterodactyl\Transformers\UserTransformer; -use Pterodactyl\Repositories\UserRepository; - -use Pterodactyl\Exceptions\DisplayValidationException; -use Pterodactyl\Exceptions\DisplayException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; - -/** - * @Resource("Users") - */ -class UserController extends BaseController -{ - - public function __construct() - { - - } - - /** - * List All Users - * - * Lists all users currently on the system. - * - * @Get("/users/{?page}") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("page", type="integer", description="The page of results to view.", default=1) - * }) - * @Response(200) - */ - public function list(Request $request) - { - return Models\User::all()->toArray(); - } - - /** - * List Specific User - * - * Lists specific fields about a user or all fields pertaining to that user. - * - * @Get("/users/{id}/{fields}") - * @Versions({"v1"}) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the user to get information on."), - * @Parameter("fields", type="string", required=false, description="A comma delimidated list of fields to include.") - * }) - * @Response(200) - */ - public function view(Request $request, $id) - { - $query = Models\User::where('id', $id); - - if (!is_null($request->input('fields'))) { - foreach(explode(',', $request->input('fields')) as $field) { - if (!empty($field)) { - $query->addSelect($field); - } - } - } - - try { - if (!$query->first()) { - throw new NotFoundHttpException('No user by that ID was found.'); - } - - $user = $query->first(); - $userArray = $user->toArray(); - $userArray['servers'] = Models\Server::select('id', 'uuid', 'node', 'suspended')->where('owner', $user->id)->get(); - - return $userArray; - } catch (NotFoundHttpException $ex) { - throw $ex; - } catch (\Exception $ex) { - throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); - } - - } - - /** - * Create a New User - * - * @Post("/users") - * @Versions({"v1"}) - * @Transaction({ - * @Request({ - * "email": "foo@example.com", - * "password": "foopassword", - * "admin": false, - * "custom_id": 123 - * }, headers={"Authorization": "Bearer "}), - * @Response(201), - * @Response(422) - * }) - */ - public function create(Request $request) - { - try { - $user = new UserRepository; - $create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id')); - return [ 'id' => $create ]; - } catch (DisplayValidationException $ex) { - throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $ex) { - throw new ServiceUnavailableHttpException('Unable to create a user on the system due to an error.'); - } - } - - /** - * Update an Existing User - * - * The data sent in the request will be used to update the existing user on the system. - * - * @Patch("/users/{id}") - * @Versions({"v1"}) - * @Transaction({ - * @Request({ - * "email": "new@email.com" - * }, headers={"Authorization": "Bearer "}), - * @Response(200, body={"email": "new@email.com"}), - * @Response(422) - * }) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the user to modify.") - * }) - */ - public function update(Request $request, $id) - { - try { - $user = new UserRepository; - $user->update($id, $request->all()); - return Models\User::findOrFail($id); - } catch (DisplayValidationException $ex) { - throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $ex) { - throw new ServiceUnavailableHttpException('Unable to update a user on the system due to an error.'); - } - } - - /** - * Delete a User - * - * @Delete("/users/{id}") - * @Versions({"v1"}) - * @Transaction({ - * @Request(headers={"Authorization": "Bearer "}), - * @Response(204), - * @Response(422) - * }) - * @Parameters({ - * @Parameter("id", type="integer", required=true, description="The ID of the user to delete.") - * }) - */ - public function delete(Request $request, $id) - { - try { - $user = new UserRepository; - $user->delete($id); - return $this->response->noContent(); - } catch (DisplayException $ex) { - throw new ResourceException($ex->getMessage()); - } catch (\Exception $ex) { - throw new ServiceUnavailableHttpException('Unable to delete this user due to an error.'); - } - } - -} diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php index f44ac06f4..8c5719827 100644 --- a/app/Http/Controllers/Admin/BaseController.php +++ b/app/Http/Controllers/Admin/BaseController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,43 +21,50 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Admin; use Alert; use Settings; use Validator; - -use Pterodactyl\Http\Controllers\Controller; use Illuminate\Http\Request; +use Pterodactyl\Http\Controllers\Controller; class BaseController extends Controller { - /** - * Controller Constructor + * Return the admin index view. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ - public function __construct() - { - // - } - public function getIndex(Request $request) { return view('admin.index'); } + /** + * Return the admin settings view. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ public function getSettings(Request $request) { return view('admin.settings'); } + /** + * Handle settings post request. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ public function postSettings(Request $request) { $validator = Validator::make($request->all(), [ 'company' => 'required|between:1,256', - 'default_language' => 'required|alpha_dash|min:2|max:5', - 'email_from' => 'required|email', - 'email_sender_name' => 'required|between:1,256' + // 'default_language' => 'required|alpha_dash|min:2|max:5', ]); if ($validator->fails()) { @@ -65,13 +72,10 @@ class BaseController extends Controller } Settings::set('company', $request->input('company')); - Settings::set('default_language', $request->input('default_language')); - Settings::set('email_from', $request->input('email_from')); - Settings::set('email_sender_name', $request->input('email_sender_name')); + // Settings::set('default_language', $request->input('default_language')); Alert::success('Settings have been successfully updated.')->flash(); + return redirect()->route('admin.settings'); - } - } diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index e30278f81..6b4e12a1d 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,110 +21,115 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Admin; -use Alert; -use DB; use Log; - -use Pterodactyl\Models; -use Pterodactyl\Repositories\DatabaseRepository; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -use Pterodactyl\Http\Controllers\Controller; +use Alert; use Illuminate\Http\Request; +use Pterodactyl\Models\Database; +use Pterodactyl\Models\Location; +use Pterodactyl\Models\DatabaseHost; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\DatabaseRepository; +use Pterodactyl\Exceptions\DisplayValidationException; class DatabaseController extends Controller { - /** - * Controller Constructor + * Display database host index. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ - public function __construct() - { - // - } - - public function getIndex(Request $request) + public function index(Request $request) { return view('admin.databases.index', [ - 'databases' => Models\Database::select( - 'databases.*', - 'database_servers.host as a_host', - 'database_servers.port as a_port', - 'servers.id as a_serverId', - 'servers.name as a_serverName' - )->join('database_servers', 'database_servers.id', '=', 'databases.db_server') - ->join('servers', 'databases.server_id', '=', 'servers.id') - ->paginate(20), - 'dbh' => Models\DatabaseServer::select( - 'database_servers.*', - 'nodes.name as a_linkedNode', - DB::raw('(SELECT COUNT(*) FROM `databases` WHERE `databases`.`db_server` = database_servers.id) as c_databases') - )->leftJoin('nodes', 'nodes.id', '=', 'database_servers.linked_node') - ->paginate(20) + 'locations' => Location::with('nodes')->get(), + 'hosts' => DatabaseHost::withCount('databases')->with('node')->get(), ]); } - public function getNew(Request $request) + /** + * Display database host to user. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function view(Request $request, $id) { - return view('admin.databases.new', [ - 'nodes' => Models\Node::select('nodes.id', 'nodes.name', 'locations.long as a_location') - ->join('locations', 'locations.id', '=', 'nodes.location') - ->get() + return view('admin.databases.view', [ + 'locations' => Location::with('nodes')->get(), + 'host' => DatabaseHost::with('databases.server')->findOrFail($id), ]); } - public function postNew(Request $request) + /** + * Handle post request to create database host. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function create(Request $request) { + $repo = new DatabaseRepository; + try { - $repo = new DatabaseRepository; - $repo->add($request->except([ - '_token' + $host = $repo->add($request->intersect([ + 'name', 'username', 'password', + 'host', 'port', 'node_id', ])); + Alert::success('Successfully created new database host on the system.')->flash(); - Alert::success('Successfully added a new database server to the system.')->flash(); - return redirect()->route('admin.databases', [ - 'tab' => 'tab_dbservers' - ]); + return redirect()->route('admin.databases.view', $host->id); + } catch (\PDOException $ex) { + Alert::danger($ex->getMessage())->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.databases.new')->withErrors(json_decode($ex->getMessage()))->withInput(); + return redirect()->route('admin.databases')->withErrors(json_decode($ex->getMessage())); } catch (\Exception $ex) { - if ($ex instanceof DisplayException || $ex instanceof \PDOException) { - Alert::danger($ex->getMessage())->flash(); + Log::error($ex); + Alert::danger('An error was encountered while trying to process this request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.databases'); + } + + /** + * Handle post request to update a database host. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function update(Request $request, $id) + { + $repo = new DatabaseRepository; + + try { + if ($request->input('action') !== 'delete') { + $host = $repo->update($id, $request->intersect([ + 'name', 'username', 'password', + 'host', 'port', 'node_id', + ])); + Alert::success('Database host was updated successfully.')->flash(); } else { - Log::error($ex); - Alert::danger('An error occurred while attempting to delete this database server from the system.')->flash(); + $repo->delete($id); + + return redirect()->route('admin.databases'); } - return redirect()->route('admin.databases.new')->withInput(); - } - } - - public function deleteDatabase(Request $request, $id) - { - try { - $repo = new DatabaseRepository; - $repo->drop($id); + } catch (\PDOException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.databases.view', $id)->withErrors(json_decode($ex->getMessage())); } catch (\Exception $ex) { Log::error($ex); - return response()->json([ - 'error' => ($ex instanceof DisplayException) ? $ex->getMessage() : 'An error occurred while attempting to delete this database from the system.' - ], 500); + Alert::danger('An error was encountered while trying to process this request. This error has been logged.')->flash(); } - } - public function deleteServer(Request $request, $id) - { - try { - $repo = new DatabaseRepository; - $repo->delete($id); - } catch (\Exception $ex) { - Log::error($ex); - return response()->json([ - 'error' => ($ex instanceof DisplayException) ? $ex->getMessage() : 'An error occurred while attempting to delete this database server from the system.' - ], 500); - } + return redirect()->route('admin.databases.view', $id); } - } diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php new file mode 100644 index 000000000..4e602f7ca --- /dev/null +++ b/app/Http/Controllers/Admin/LocationController.php @@ -0,0 +1,119 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Admin; + +use Log; +use Alert; +use Illuminate\Http\Request; +use Pterodactyl\Models\Location; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\LocationRepository; +use Pterodactyl\Exceptions\DisplayValidationException; + +class LocationController extends Controller +{ + /** + * Return the location overview page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function index(Request $request) + { + return view('admin.locations.index', [ + 'locations' => Location::withCount('nodes', 'servers')->get(), + ]); + } + + /** + * Return the location view page. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function view(Request $request, $id) + { + return view('admin.locations.view', ['location' => Location::with('nodes.servers')->findOrFail($id)]); + } + + /** + * Handle request to create new location. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function create(Request $request) + { + $repo = new LocationRepository; + + try { + $location = $repo->create($request->intersect(['short', 'long'])); + Alert::success('Location was created successfully.')->flash(); + + return redirect()->route('admin.locations.view', $location->id); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.locations')->withErrors(json_decode($ex->getMessage())); + } catch (\Exception $ex) { + Log::error($ex); + Alert::error('An unhandled exception occurred while processing this request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.locations'); + } + + /** + * Handle request to update or delete location. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function update(Request $request, $id) + { + $repo = new LocationRepository; + + try { + if ($request->input('action') !== 'delete') { + $location = $repo->update($id, $request->intersect(['short', 'long'])); + Alert::success('Location was updated successfully.')->flash(); + } else { + $repo->delete($id); + + return redirect()->route('admin.locations'); + } + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.locations.view', $id)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::error('An unhandled exception occurred while processing this request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.locations.view', $id); + } +} diff --git a/app/Http/Controllers/Admin/LocationsController.php b/app/Http/Controllers/Admin/LocationsController.php deleted file mode 100644 index 86f32c8e6..000000000 --- a/app/Http/Controllers/Admin/LocationsController.php +++ /dev/null @@ -1,117 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Controllers\Admin; - -use DB; -use Alert; - -use Pterodactyl\Models; -use Pterodactyl\Repositories\LocationRepository; -use Pterodactyl\Http\Controllers\Controller; - -use Pterodactyl\Exceptions\DisplayValidationException; -use Pterodactyl\Exceptions\DisplayException; - -use Illuminate\Http\Request; - -class LocationsController extends Controller -{ - - public function __construct() - { - // - } - - public function getIndex(Request $request) - { - return view('admin.locations.index', [ - 'locations' => Models\Location::select( - 'locations.*', - DB::raw('(SELECT COUNT(*) FROM nodes WHERE nodes.location = locations.id) as a_nodeCount'), - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node IN (SELECT nodes.id FROM nodes WHERE nodes.location = locations.id)) as a_serverCount') - )->paginate(20) - ]); - } - - public function deleteLocation(Request $request, $id) - { - $model = Models\Location::select( - 'locations.id', - DB::raw('(SELECT COUNT(*) FROM nodes WHERE nodes.location = locations.id) as a_nodeCount'), - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node IN (SELECT nodes.id FROM nodes WHERE nodes.location = locations.id)) as a_serverCount') - )->where('id', $id)->first(); - - if (!$model) { - return response()->json([ - 'error' => 'No location with that ID exists on the system.' - ], 404); - } - - if ($model->a_nodeCount > 0 || $model->a_serverCount > 0) { - return response()->json([ - 'error' => 'You cannot remove a location that is currently assigned to a node or server.' - ], 422); - } - - $model->delete(); - return response('', 204); - } - - public function patchLocation(Request $request, $id) - { - try { - $location = new LocationRepository; - $location->edit($id, $request->all()); - return response('', 204); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => 'There was a validation error while processing this request. Location descriptions must be between 1 and 255 characters, and the location code must be between 1 and 10 characters with no spaces or special characters.' - ], 422); - } catch (\Exception $ex) { - // This gets caught and processed into JSON anyways. - throw $ex; - } - } - - public function postLocation(Request $request) - { - try { - $location = new LocationRepository; - $id = $location->create($request->except([ - '_token' - ])); - Alert::success('New location successfully added.')->flash(); - return redirect()->route('admin.locations'); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.locations')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to add this location. Please try again.')->flash(); - } - return redirect()->route('admin.locations')->withInput(); - } - -} diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 24ac37614..ca4836ea4 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,73 +21,82 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Admin; -use Alert; -use Debugbar; -use Log; use DB; -use Validator; - +use Log; +use Alert; +use Cache; +use Javascript; use Pterodactyl\Models; -use Pterodactyl\Repositories\NodeRepository; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -use Pterodactyl\Http\Controllers\Controller; use Illuminate\Http\Request; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\NodeRepository; +use Pterodactyl\Exceptions\DisplayValidationException; class NodesController extends Controller { + /** + * Displays the index page listing all nodes on the panel. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function index(Request $request) + { + $nodes = Models\Node::with('location')->withCount('servers'); + + if (! is_null($request->input('query'))) { + $nodes->search($request->input('query')); + } + + return view('admin.nodes.index', ['nodes' => $nodes->paginate(25)]); + } /** - * Controller Constructor + * Displays create new node page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse */ - public function __construct() + public function create(Request $request) { - // - } - - public function getScript(Request $request, $id) - { - return response()->view('admin.nodes.remote.deploy', [ 'node' => Models\Node::findOrFail($id) ])->header('Content-Type', 'text/plain'); - } - - public function getIndex(Request $request) - { - return view('admin.nodes.index', [ - 'nodes' => Models\Node::select( - 'nodes.*', - 'locations.long as a_locationName', - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node = nodes.id) as a_serverCount') - )->join('locations', 'nodes.location', '=', 'locations.id')->paginate(20), - ]); - } - - public function getNew(Request $request) - { - if (!Models\Location::all()->count()) { + $locations = Models\Location::all(); + if (! $locations->count()) { Alert::warning('You must add a location before you can add a new node.')->flash(); + return redirect()->route('admin.locations'); } - return view('admin.nodes.new', [ - 'locations' => Models\Location::all() - ]); + return view('admin.nodes.new', ['locations' => $locations]); } - public function postNew(Request $request) + /** + * Post controller to create a new node on the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(Request $request) { try { - $node = new NodeRepository; - $new = $node->create($request->except([ - '_token' - ])); - Alert::success('Successfully created new node. Before you can add any servers you need to first assign some IP addresses and ports.')->flash(); - return redirect()->route('admin.nodes.view', [ - 'id' => $new, - 'tab' => 'tab_allocation' - ]); + $repo = new NodeRepository; + $node = $repo->create(array_merge( + $request->only([ + 'public', 'disk_overallocate', + 'memory_overallocate', 'behind_proxy', + ]), + $request->intersect([ + 'name', 'location_id', 'fqdn', + 'scheme', 'memory', 'disk', + 'daemonBase', 'daemonSFTP', 'daemonListen', + ]) + )); + Alert::success('Successfully created new node that can be configured automatically on your remote machine by visiting the configuration tab. Before you can add any servers you need to first assign some IP addresses and ports.')->flash(); + + return redirect()->route('admin.nodes.view', $node->id); } catch (DisplayValidationException $e) { return redirect()->route('admin.nodes.new')->withErrors(json_decode($e->getMessage()))->withInput(); } catch (DisplayException $e) { @@ -96,96 +105,201 @@ class NodesController extends Controller Log::error($e); Alert::danger('An unhandled exception occured while attempting to add this node. Please try again.')->flash(); } + return redirect()->route('admin.nodes.new')->withInput(); } - public function getView(Request $request, $id) + /** + * Shows the index overview page for a specific node. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewIndex(Request $request, $id) { - $node = Models\Node::findOrFail($id); + $node = Models\Node::with('location')->withCount('servers')->findOrFail($id); + $stats = collect( + Models\Server::select( + DB::raw('SUM(memory) as memory, SUM(disk) as disk') + )->where('node_id', $node->id)->first() + )->mapWithKeys(function ($item, $key) use ($node) { + if ($node->{$key . '_overallocate'} > 0) { + $withover = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); + } else { + $withover = $node->{$key}; + } - return view('admin.nodes.view', [ - 'node' => $node, - 'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail', 'services.name as a_serviceName') - ->join('users', 'users.id', '=', 'servers.owner') - ->join('services', 'services.id', '=', 'servers.service') - ->where('node', $id)->paginate(10, ['*'], 'servers'), - 'stats' => Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(), + $percent = ($item / $withover) * 100; + + return [$key => [ + 'value' => number_format($item), + 'max' => number_format($withover), + 'percent' => $percent, + 'css' => ($percent <= 75) ? 'green' : (($percent > 90) ? 'red' : 'yellow'), + ]]; + })->toArray(); + + return view('admin.nodes.view.index', ['node' => $node, 'stats' => $stats]); + } + + /** + * Shows the settings page for a specific node. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewSettings(Request $request, $id) + { + return view('admin.nodes.view.settings', [ + 'node' => Models\Node::findOrFail($id), 'locations' => Models\Location::all(), - 'allocations' => Models\Allocation::select('allocations.*', 'servers.name as assigned_to_name') - ->where('allocations.node', $node->id) - ->leftJoin('servers', 'servers.id', '=', 'allocations.assigned_to') - ->orderBy('allocations.ip', 'asc') - ->orderBy('allocations.port', 'asc') - ->paginate(20, ['*'], 'allocations'), - 'allocation_ips' => Models\Allocation::select('id', 'ip') - ->where('node', $node->id) - ->groupBy('ip') - ->get(), ]); } - public function postView(Request $request, $id) + /** + * Shows the configuration page for a specific node. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewConfiguration(Request $request, $id) { - try { - $node = new NodeRepository; - $node->update($id, $request->except([ - '_token' - ])); - Alert::success('Successfully update this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash(); - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_settings' - ]); - } catch (DisplayValidationException $e) { - return redirect()->route('admin.nodes.view', $id)->withErrors(json_decode($e->getMessage()))->withInput(); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attempting to edit this node. Please try again.')->flash(); - } - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_settings' - ])->withInput(); + return view('admin.nodes.view.configuration', [ + 'node' => Models\Node::findOrFail($id), + ]); } - public function deallocateSingle(Request $request, $node, $allocation) + /** + * Shows the allocation page for a specific node. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewAllocation(Request $request, $id) { - $query = Models\Allocation::where('node', $node)->whereNull('assigned_to')->where('id', $allocation)->delete(); - if ((int) $query === 0) { + $node = Models\Node::findOrFail($id); + $node->setRelation('allocations', $node->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc')->with('server')->paginate(50)); + + Javascript::put([ + 'node' => collect($node)->only(['id']), + ]); + + return view('admin.nodes.view.allocation', ['node' => $node]); + } + + /** + * Shows the server listing page for a specific node. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewServers(Request $request, $id) + { + $node = Models\Node::with('servers.user', 'servers.service', 'servers.option')->findOrFail($id); + Javascript::put([ + 'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']), + ]); + + return view('admin.nodes.view.servers', [ + 'node' => $node, + ]); + } + + /** + * Updates settings for a node. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function updateSettings(Request $request, $id) + { + $repo = new NodeRepository; + + try { + $node = $repo->update($id, array_merge( + $request->only([ + 'public', 'disk_overallocate', + 'memory_overallocate', 'behind_proxy', + ]), + $request->intersect([ + 'name', 'location_id', 'fqdn', + 'scheme', 'memory', 'disk', 'upload_size', + 'reset_secret', 'daemonSFTP', 'daemonListen', + ]) + )); + Alert::success('Successfully updated this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.nodes.view.settings', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attempting to edit this node. Please try again.')->flash(); + } + + return redirect()->route('admin.nodes.view.settings', $id)->withInput(); + } + + /** + * Removes a single allocation from a node. + * + * @param \Illuminate\Http\Request $request + * @param int $node + * @param int $allocation + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function allocationRemoveSingle(Request $request, $node, $allocation) + { + $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('id', $allocation)->delete(); + if ($query < 1) { return response()->json([ - 'error' => 'Unable to find an allocation matching those details to delete.' + 'error' => 'Unable to find an allocation matching those details to delete.', ], 400); } + return response('', 204); } - public function deallocateBlock(Request $request, $node) + /** + * Remove all allocations for a specific IP at once on a node. + * + * @param \Illuminate\Http\Request $request + * @param int $node + * @return \Illuminate\Http\RedirectResponse + */ + public function allocationRemoveBlock(Request $request, $node) { - $query = Models\Allocation::where('node', $node)->whereNull('assigned_to')->where('ip', $request->input('ip'))->delete(); - if ((int) $query === 0) { + $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('ip', $request->input('ip'))->delete(); + if ($query < 1) { Alert::danger('There was an error while attempting to delete allocations on that IP.')->flash(); - return redirect()->route('admin.nodes.view', [ - 'id' => $node, - 'tab' => 'tab_allocations' - ]); + } else { + Alert::success('Deleted all unallocated ports for ' . $request->input('ip') . '.')->flash(); } - Alert::success('Deleted all unallocated ports for ' . $request->input('ip') . '.')->flash(); - return redirect()->route('admin.nodes.view', [ - 'id' => $node, - 'tab' => 'tab_allocation' - ]); + + return redirect()->route('admin.nodes.view.allocation', $node); } - public function setAlias(Request $request, $node) + /** + * Sets an alias for a specific allocation on a node. + * + * @param \Illuminate\Http\Request $request + * @param int $node + * @return \Illuminate\Http\Response + */ + public function allocationSetAlias(Request $request, $node) { - if (!$request->input('allocation')) { + if (! $request->input('allocation_id')) { return response('Missing required parameters.', 422); } try { - $update = Models\Allocation::findOrFail($request->input('allocation')); + $update = Models\Allocation::findOrFail($request->input('allocation_id')); $update->ip_alias = (empty($request->input('alias'))) ? null : $request->input('alias'); $update->save(); @@ -195,78 +309,72 @@ class NodesController extends Controller } } - public function getAllocationsJson(Request $request, $id) + /** + * Creates new allocations on a node. + * + * @param \Illuminate\Http\Request $request + * @param int $node + * @return \Illuminate\Http\RedirectResponse + */ + public function createAllocation(Request $request, $node) { - $allocations = Models\Allocation::select('ip')->where('node', $id)->groupBy('ip')->get(); - return response()->json($allocations); - } - - public function postAllocations(Request $request, $id) - { - - $validator = Validator::make($request->all(), [ - 'allocate_ip.*' => 'required|string', - 'allocate_port.*' => 'required' - ]); - - if ($validator->fails()) { - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_allocation' - ])->withErrors($validator->errors())->withInput(); - } - - $processedData = []; - foreach($request->input('allocate_ip') as $ip) { - if (!array_key_exists($ip, $processedData)) { - $processedData[$ip] = []; - } - } - - foreach($request->input('allocate_port') as $portid => $ports) { - if (array_key_exists($portid, $request->input('allocate_ip'))) { - $json = json_decode($ports); - if (json_last_error() === 0 && !empty($json)) { - foreach($json as &$parsed) { - array_push($processedData[$request->input('allocate_ip')[$portid]], $parsed->value); - } - } - } - } + $repo = new NodeRepository; try { - $node = new NodeRepository; - $node->addAllocations($id, $processedData); - Alert::success('Successfully added new allocations to this node.')->flash(); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attempting to add allocations this node. Please try again.')->flash(); - } finally { - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_allocation' - ]); + $repo->addAllocations($node, $request->intersect(['allocation_ip', 'allocation_alias', 'allocation_ports'])); + Alert::success('Successfully added new allocations!')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.nodes.view.allocation', $node)->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attempting to add allocations this node. This error has been logged.')->flash(); } + + return redirect()->route('admin.nodes.view.allocation', $node); } - public function deleteNode(Request $request, $id) + /** + * Deletes a node from the system. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function delete(Request $request, $id) + { + $repo = new NodeRepository; + + try { + $repo->delete($id); + Alert::success('Successfully deleted the requested node from the panel.')->flash(); + + return redirect()->route('admin.nodes'); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attempting to delete this node. Please try again.')->flash(); + } + + return redirect()->route('admin.nodes.view', $id); + } + + /** + * Returns the configuration token to auto-deploy a node. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\JsonResponse + */ + public function setToken(Request $request, $id) { $node = Models\Node::findOrFail($id); - $servers = Models\Server::where('node', $id)->count(); - if ($servers > 0) { - Alert::danger('You cannot delete a node with servers currently attached to it.')->flash(); - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_delete' - ]); - } - $node->delete(); - Alert::success('Node successfully deleted.')->flash(); - return redirect()->route('admin.nodes'); + $token = str_random(32); + Cache::tags(['Node:Configuration'])->put($token, $node->id, 5); + return response()->json(['token' => $token]); } - } diff --git a/app/Http/Controllers/Admin/OptionController.php b/app/Http/Controllers/Admin/OptionController.php new file mode 100644 index 000000000..41c4544a1 --- /dev/null +++ b/app/Http/Controllers/Admin/OptionController.php @@ -0,0 +1,261 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Admin; + +use Log; +use Alert; +use Javascript; +use Illuminate\Http\Request; +use Pterodactyl\Models\Service; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\OptionRepository; +use Pterodactyl\Repositories\VariableRepository; +use Pterodactyl\Exceptions\DisplayValidationException; + +class OptionController extends Controller +{ + /** + * Handles request to view page for adding new option. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function create(Request $request) + { + $services = Service::with('options')->get(); + Javascript::put(['services' => $services->keyBy('id')]); + + return view('admin.services.options.new', ['services' => $services]); + } + + /** + * Handles POST request to create a new option. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Response\RedirectResponse + */ + public function store(Request $request) + { + $repo = new OptionRepository; + + try { + $option = $repo->create($request->intersect([ + 'service_id', 'name', 'description', 'tag', + 'docker_image', 'startup', 'config_from', 'config_startup', + 'config_logs', 'config_files', 'config_stop', + ])); + Alert::success('Successfully created new service option.')->flash(); + + return redirect()->route('admin.services.option.view', $option->id); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.option.new')->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occurred while attempting to create this service. This error has been logged.')->flash(); + } + + return redirect()->route('admin.services.option.new')->withInput(); + } + + /** + * Handles POST request to create a new option variable. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function createVariable(Request $request, $id) + { + $repo = new VariableRepository; + + try { + $variable = $repo->create($id, $request->intersect([ + 'name', 'description', 'env_variable', + 'default_value', 'options', 'rules', + ])); + + Alert::success('New variable successfully assigned to this service option.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.option.variables', $id)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.services.option.variables', $id); + } + + /** + * Display option overview page. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewConfiguration(Request $request, $id) + { + return view('admin.services.options.view', ['option' => ServiceOption::findOrFail($id)]); + } + + /** + * Display variable overview page for a service option. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewVariables(Request $request, $id) + { + return view('admin.services.options.variables', ['option' => ServiceOption::with('variables')->findOrFail($id)]); + } + + /** + * Display script management page for an option. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewScripts(Request $request, $id) + { + $option = ServiceOption::with('copyFrom')->findOrFail($id); + + return view('admin.services.options.scripts', [ + 'copyFromOptions' => ServiceOption::whereNull('copy_script_from')->where([ + ['service_id', $option->service_id], + ['id', '!=', $option->id], + ])->get(), + 'relyOnScript' => ServiceOption::where('copy_script_from', $option->id)->get(), + 'option' => $option, + ]); + } + + /** + * Handles POST when editing a configration for a service option. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function editConfiguration(Request $request, $id) + { + $repo = new OptionRepository; + + try { + if ($request->input('action') !== 'delete') { + $repo->update($id, $request->intersect([ + 'name', 'description', 'tag', 'docker_image', 'startup', + 'config_from', 'config_stop', 'config_logs', 'config_files', 'config_startup', + ])); + Alert::success('Service option configuration has been successfully updated.')->flash(); + } else { + $option = ServiceOption::with('service')->where('id', $id)->first(); + $repo->delete($id); + Alert::success('Successfully deleted service option from the system.')->flash(); + + return redirect()->route('admin.services.view', $option->service_id); + } + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.option.view', $id)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occurred while attempting to perform that action. This error has been logged.')->flash(); + } + + return redirect()->route('admin.services.option.view', $id); + } + + /** + * Handles POST when editing a configration for a service option. + * + * @param \Illuminate\Http\Request $request + * @param int $option + * @param int $variable + * @return \Illuminate\Http\RedirectResponse + */ + public function editVariable(Request $request, $option, $variable) + { + $repo = new VariableRepository; + + try { + if ($request->input('action') !== 'delete') { + $variable = $repo->update($variable, $request->only([ + 'name', 'description', 'env_variable', + 'default_value', 'options', 'rules', + ])); + Alert::success("The service variable '{$variable->name}' has been updated.")->flash(); + } else { + $repo->delete($variable); + Alert::success('That service variable has been deleted.')->flash(); + } + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.option.variables', $option)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.services.option.variables', $option); + } + + /** + * Handles POST when updating scripts for a service option. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function updateScripts(Request $request, $id) + { + $repo = new OptionRepository; + + try { + $repo->scripts($id, $request->only([ + 'script_install', 'script_entry', + 'script_container', 'copy_script_from', + ])); + Alert::success('Successfully updated option scripts to be run when servers are installed.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.option.scripts', $id)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.services.option.scripts', $id); + } +} diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php new file mode 100644 index 000000000..d02273672 --- /dev/null +++ b/app/Http/Controllers/Admin/PackController.php @@ -0,0 +1,214 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Admin; + +use Log; +use Alert; +use Storage; +use Illuminate\Http\Request; +use Pterodactyl\Models\Pack; +use Pterodactyl\Models\Service; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\PackRepository; +use Pterodactyl\Exceptions\DisplayValidationException; + +class PackController extends Controller +{ + /** + * Display listing of all packs on the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function index(Request $request) + { + $packs = Pack::with('option')->withCount('servers'); + + if (! is_null($request->input('query'))) { + $packs->search($request->input('query')); + } + + return view('admin.packs.index', ['packs' => $packs->paginate(50)]); + } + + /** + * Display new pack creation form. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function create(Request $request) + { + return view('admin.packs.new', [ + 'services' => Service::with('options')->get(), + ]); + } + + /** + * Display new pack creation modal for use with template upload. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function newTemplate(Request $request) + { + return view('admin.packs.modal', [ + 'services' => Service::with('options')->get(), + ]); + } + + /** + * Handle create pack request and route user to location. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function store(Request $request) + { + $repo = new PackRepository; + + try { + if ($request->input('action') === 'from_template') { + $pack = $repo->createWithTemplate($request->intersect(['option_id', 'file_upload'])); + } else { + $pack = $repo->create($request->intersect([ + 'name', 'description', 'version', 'option_id', + 'selectable', 'visible', 'locked', 'file_upload', + ])); + } + Alert::success('Pack successfully created on the system.')->flash(); + + return redirect()->route('admin.packs.view', $pack->id); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.packs.new')->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An error occured while attempting to add a new service pack. This error has been logged.')->flash(); + } + + return redirect()->route('admin.packs.new')->withInput(); + } + + /** + * Display pack view template to user. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function view(Request $request, $id) + { + return view('admin.packs.view', [ + 'pack' => Pack::with('servers.node', 'servers.user')->findOrFail($id), + 'services' => Service::with('options')->get(), + ]); + } + + /** + * Handle updating or deleting pack information. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function update(Request $request, $id) + { + $repo = new PackRepository; + + try { + if ($request->input('action') !== 'delete') { + $pack = $repo->update($id, $request->intersect([ + 'name', 'description', 'version', + 'option_id', 'selectable', 'visible', 'locked', + ])); + Alert::success('Pack successfully updated.')->flash(); + } else { + $repo->delete($id); + Alert::success('Pack was successfully deleted from the system.')->flash(); + + return redirect()->route('admin.packs'); + } + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.packs.view', $id)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An error occured while attempting to edit this service pack. This error has been logged.')->flash(); + } + + return redirect()->route('admin.packs.view', $id); + } + + /** + * Creates an archive of the pack and downloads it to the browser. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @param bool $files + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public function export(Request $request, $id, $files = false) + { + $pack = Pack::findOrFail($id); + $json = [ + 'name' => $pack->name, + 'version' => $pack->version, + 'description' => $pack->description, + 'selectable' => $pack->selectable, + 'visible' => $pack->visible, + 'locked' => $pack->locked, + ]; + + $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); + if ($files === 'with-files') { + $zip = new \ZipArchive; + if (! $zip->open($filename, \ZipArchive::CREATE)) { + abort(503, 'Unable to open file for writing.'); + } + + $files = Storage::files('packs/' . $pack->uuid); + foreach ($files as $file) { + $zip->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file))); + } + + $zip->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT)); + $zip->close(); + + return response()->download($filename, 'pack-' . $pack->name . '.zip')->deleteFileAfterSend(true); + } else { + $fp = fopen($filename, 'a+'); + fwrite($fp, json_encode($json, JSON_PRETTY_PRINT)); + fclose($fp); + + return response()->download($filename, 'pack-' . $pack->name . '.json', [ + 'Content-Type' => 'application/json', + ])->deleteFileAfterSend(true); + } + } +} diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 8f0b184c6..76715c84c 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,520 +21,580 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Admin; -use Alert; -use Debugbar; -use DB; use Log; - +use Alert; +use Javascript; use Pterodactyl\Models; +use Illuminate\Http\Request; +use GuzzleHttp\Exception\TransferException; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\ServerRepository; use Pterodactyl\Repositories\DatabaseRepository; - -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\AutoDeploymentException; use Pterodactyl\Exceptions\DisplayValidationException; -use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Http\Request; - class ServersController extends Controller { - /** - * Controller Constructor + * Display the index page with all servers currently on the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ - public function __construct() + public function index(Request $request) { - // - } + $servers = Models\Server::with('node', 'user', 'allocation'); - public function getIndex(Request $request) - { - $query = Models\Server::withTrashed()->select( - 'servers.*', - 'nodes.name as a_nodeName', - 'users.email as a_ownerEmail', - 'allocations.ip', - 'allocations.port', - 'allocations.ip_alias' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('users', 'servers.owner', '=', 'users.id') - ->join('allocations', 'servers.allocation', '=', 'allocations.id'); - - if ($request->input('filter') && !is_null($request->input('filter'))) { - preg_match_all('/[^\s"\']+|"([^"]*)"|\'([^\']*)\'/', urldecode($request->input('filter')), $matches); - foreach($matches[0] as $match) { - $match = str_replace('"', '', $match); - if (strpos($match, ':')) { - list($field, $term) = explode(':', $match); - $field = (strpos($field, '.')) ? $field : 'servers.' . $field; - $query->orWhere($field, 'LIKE', '%' . $term . '%'); - } else { - $query->where('servers.name', 'LIKE', '%' . $match . '%'); - $query->orWhere('servers.username', 'LIKE', '%' . $match . '%'); - $query->orWhere('users.email', 'LIKE', '%' . $match . '%'); - $query->orWhere('allocations.port', 'LIKE', '%' . $match . '%'); - $query->orWhere('allocations.ip', 'LIKE', '%' . $match . '%'); - } - } - } - - try { - $servers = $query->paginate(20); - } catch (\Exception $ex) { - Alert::warning('There was an error with the search parameters provided.'); - $servers = Models\Server::withTrashed()->select( - 'servers.*', - 'nodes.name as a_nodeName', - 'users.email as a_ownerEmail', - 'allocations.ip', - 'allocations.port', - 'allocations.ip_alias' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('users', 'servers.owner', '=', 'users.id') - ->join('allocations', 'servers.allocation', '=', 'allocations.id') - ->paginate(20); + if (! is_null($request->input('query'))) { + $servers->search($request->input('query')); } return view('admin.servers.index', [ - 'servers' => $servers + 'servers' => $servers->paginate(25), ]); } - public function getNew(Request $request) + /** + * Display create new server page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function create(Request $request) { + $services = Models\Service::with('options.packs', 'options.variables')->get(); + Javascript::put([ + 'services' => $services->map(function ($item) { + return array_merge($item->toArray(), [ + 'options' => $item->options->keyBy('id')->toArray(), + ]); + })->keyBy('id'), + ]); + return view('admin.servers.new', [ 'locations' => Models\Location::all(), - 'services' => Models\Service::all() + 'services' => $services, ]); } - public function getView(Request $request, $id) + /** + * Create server controller method. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Response\RedirectResponse + */ + public function store(Request $request) { - $server = Models\Server::withTrashed()->select( - 'servers.*', - 'nodes.name as a_nodeName', - 'users.email as a_ownerEmail', - 'locations.long as a_locationName', - 'services.name as a_serviceName', - DB::raw('IFNULL(service_options.executable, services.executable) as a_serviceExecutable'), - 'service_options.docker_image', - 'service_options.name as a_servceOptionName', - 'allocations.ip', - 'allocations.port', - 'allocations.ip_alias' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('users', 'servers.owner', '=', 'users.id') - ->join('locations', 'nodes.location', '=', 'locations.id') - ->join('services', 'servers.service', '=', 'services.id') - ->join('service_options', 'servers.option', '=', 'service_options.id') - ->join('allocations', 'servers.allocation', '=', 'allocations.id') - ->where('servers.id', $id) - ->first(); - - if (!$server) { - return abort(404); - } - - return view('admin.servers.view', [ - 'server' => $server, - 'assigned' => Models\Allocation::where('assigned_to', $id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(), - 'unassigned' => Models\Allocation::where('node', $server->node)->whereNull('assigned_to')->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(), - 'startup' => Models\ServiceVariables::select('service_variables.*', 'server_variables.variable_value as a_serverValue') - ->join('server_variables', 'server_variables.variable_id', '=', 'service_variables.id') - ->where('service_variables.option_id', $server->option) - ->where('server_variables.server_id', $server->id) - ->get(), - 'databases' => Models\Database::select('databases.*', 'database_servers.host as a_host', 'database_servers.port as a_port') - ->where('server_id', $server->id) - ->join('database_servers', 'database_servers.id', '=', 'databases.db_server') - ->get(), - 'db_servers' => Models\DatabaseServer::all() - ]); - } - - public function postNewServer(Request $request) - { - try { - $server = new ServerRepository; - $response = $server->create($request->all()); - return redirect()->route('admin.servers.view', [ 'id' => $response ]); + $repo = new ServerRepository; + $server = $repo->create($request->except('_token')); + + return redirect()->route('admin.servers.view', $server->id); } catch (DisplayValidationException $ex) { return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); - return redirect()->route('admin.servers.new')->withInput(); + } catch (AutoDeploymentException $ex) { + Alert::danger('Auto-Deployment Exception: ' . $ex->getMessage())->flash(); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); } catch (\Exception $ex) { Log::error($ex); Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash(); - return redirect()->route('admin.servers.new')->withInput(); } + return redirect()->route('admin.servers.new')->withInput(); } /** - * Returns a JSON tree of all avaliable nodes in a given location. + * Returns a tree of all avaliable nodes in a given location. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @return array */ - public function postNewServerGetNodes(Request $request) + public function nodes(Request $request) { + $nodes = Models\Node::with('allocations')->where('location_id', $request->input('location'))->get(); - if(!$request->input('location')) { - return response()->json([ - 'error' => 'Missing location in request.' - ], 500); - } + return $nodes->map(function ($item) { + $filtered = $item->allocations->where('server_id', null)->map(function ($map) { + return collect($map)->only(['id', 'ip', 'port']); + }); - return response()->json(Models\Node::select('id', 'name', 'public')->where('location', $request->input('location'))->get()); + $item->ports = $filtered->map(function ($map) use ($item) { + return [ + 'id' => $map['id'], + 'text' => $map['ip'] . ':' . $map['port'], + ]; + })->values(); + return [ + 'id' => $item->id, + 'text' => $item->name, + 'allocations' => $item->ports, + ]; + })->values(); } /** - * Returns a JSON tree of all avaliable IPs and Ports on a given node. + * Display the index when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View */ - public function postNewServerGetIps(Request $request) + public function viewIndex(Request $request, $id) { - - if(!$request->input('node')) { - return response()->json([ - 'error' => 'Missing node in request.' - ], 500); - } - - $ips = Models\Allocation::where('node', $request->input('node'))->whereNull('assigned_to')->get(); - $listing = []; - - foreach($ips as &$ip) { - if (array_key_exists($ip->ip, $listing)) { - $listing[$ip->ip] = array_merge($listing[$ip->ip], [$ip->port]); - } else { - $listing[$ip->ip] = [$ip->port]; - } - } - return response()->json($listing); - + return view('admin.servers.view.index', ['server' => Models\Server::findOrFail($id)]); } /** - * Returns a JSON tree of all avaliable options for a given service. + * Display the details page when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View */ - public function postNewServerServiceOptions(Request $request) + public function viewDetails(Request $request, $id) { + $server = Models\Server::where('installed', 1)->findOrFail($id); - if(!$request->input('service')) { - return response()->json([ - 'error' => 'Missing service in request.' - ], 500); - } - - $service = Models\Service::select('executable', 'startup')->where('id', $request->input('service'))->first(); - return response()->json(Models\ServiceOptions::select('id', 'name', 'docker_image')->where('parent_service', $request->input('service'))->orderBy('name', 'asc')->get()); - + return view('admin.servers.view.details', ['server' => $server]); } /** - * Returns a JSON tree of all avaliable variables for a given service option. + * Display the build details page when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View */ - public function postNewServerServiceVariables(Request $request) + public function viewBuild(Request $request, $id) { + $server = Models\Server::where('installed', 1)->with('node.allocations')->findOrFail($id); - if(!$request->input('option')) { - return response()->json([ - 'error' => 'Missing option in request.' - ], 500); - } + return view('admin.servers.view.build', [ + 'server' => $server, + 'assigned' => $server->node->allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), + 'unassigned' => $server->node->allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), + ]); + } - $option = Models\ServiceOptions::select( - DB::raw('COALESCE(service_options.executable, services.executable) as executable'), - DB::raw('COALESCE(service_options.startup, services.startup) as startup') - )->leftJoin('services', 'services.id', '=', 'service_options.parent_service') - ->where('service_options.id', $request->input('option')) - ->first(); + /** + * Display startup configuration page for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewStartup(Request $request, $id) + { + $server = Models\Server::where('installed', 1)->with('option.variables', 'variables')->findOrFail($id); + $server->option->variables->transform(function ($item, $key) use ($server) { + $item->server_value = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); - return response()->json([ - 'variables' => Models\ServiceVariables::where('option_id', $request->input('option'))->get(), - 'exec' => $option->executable, - 'startup' => $option->startup + return $item; + }); + + $services = Models\Service::with('options.packs', 'options.variables')->get(); + Javascript::put([ + 'services' => $services->map(function ($item) { + return array_merge($item->toArray(), [ + 'options' => $item->options->keyBy('id')->toArray(), + ]); + })->keyBy('id'), + 'server_variables' => $server->variables->mapWithKeys(function ($item) { + return ['env_' . $item->variable_id => [ + 'value' => $item->variable_value, + ]]; + })->toArray(), ]); + return view('admin.servers.view.startup', [ + 'server' => $server, + 'services' => $services, + ]); } - public function postUpdateServerDetails(Request $request, $id) + /** + * Display the database management page for a specific server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewDatabase(Request $request, $id) { + $server = Models\Server::where('installed', 1)->with('databases.host')->findOrFail($id); + return view('admin.servers.view.database', [ + 'hosts' => Models\DatabaseHost::all(), + 'server' => $server, + ]); + } + + /** + * Display the management page when viewing a specific server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewManage(Request $request, $id) + { + return view('admin.servers.view.manage', ['server' => Models\Server::findOrFail($id)]); + } + + /** + * Display the deletion page for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewDelete(Request $request, $id) + { + return view('admin.servers.view.delete', ['server' => Models\Server::findOrFail($id)]); + } + + /** + * Update the details for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function setDetails(Request $request, $id) + { + $repo = new ServerRepository; try { - - $server = new ServerRepository; - $server->updateDetails($id, [ - 'owner' => $request->input('owner'), - 'name' => $request->input('name'), - 'reset_token' => ($request->input('reset_token', false) === 'on') ? true : false - ]); + $repo->updateDetails($id, array_merge( + $request->only('description'), + $request->intersect([ + 'owner_id', 'name', 'reset_token', + ]) + )); Alert::success('Server details were successfully updated.')->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_details' - ])->withErrors(json_decode($ex->getMessage()))->withInput(); + return redirect()->route('admin.servers.view.details', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update this server. Please try again.')->flash(); + Alert::danger('An unhandled exception occured while attemping to update this server. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_details' - ])->withInput(); + return redirect()->route('admin.servers.view.details', $id)->withInput(); } - public function postUpdateContainerDetails(Request $request, $id) { + /** + * Set the new docker container for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function setContainer(Request $request, $id) + { + $repo = new ServerRepository; + try { - $server = new ServerRepository; - $server->updateContainer($id, [ - 'image' => $request->input('docker_image') - ]); + $repo->updateContainer($id, $request->intersect('docker_image')); + Alert::success('Successfully updated this server\'s docker image.')->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_details' - ])->withErrors(json_decode($ex->getMessage()))->withInput(); + return redirect()->route('admin.servers.view.details', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException occured while attempting to update the container image. Is the daemon online? This error has been logged.'); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attemping to update this server\'s docker image. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.details', $id); + } + + /** + * Toggles the install status for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function toggleInstall(Request $request, $id) + { + $repo = new ServerRepository; + try { + $repo->toggleInstall($id); + + Alert::success('Server install status was successfully toggled.')->flash(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update this server\'s docker image. Please try again.')->flash(); + Alert::danger('An unhandled exception occured while attemping to toggle this servers status. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_details' - ]); + return redirect()->route('admin.servers.view.manage', $id); } - public function postUpdateServerToggleBuild(Request $request, $id) { - $server = Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); - $client = Models\Node::guzzleRequest($server->node); - - try { - $res = $client->request('POST', '/server/rebuild', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret - ] - ]); - Alert::success('A rebuild has been queued successfully. It will run the next time this server is booted.')->flash(); - } catch (\GuzzleHttp\Exception\TransferException $ex) { - Log::warning($ex); - Alert::danger('An error occured while attempting to toggle a rebuild.')->flash(); - } - - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_manage' - ]); - } - - public function postUpdateServerUpdateBuild(Request $request, $id) + /** + * Reinstalls the server with the currently assigned pack and service. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function reinstallServer(Request $request, $id) { + $repo = new ServerRepository; try { + $repo->reinstall($id); + + Alert::success('Server successfully marked for reinstallation.')->flash(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attemping to perform this reinstallation. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.manage', $id); + } + + /** + * Setup a server to have a container rebuild. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function rebuildContainer(Request $request, $id) + { + $server = Models\Server::with('node')->findOrFail($id); + + try { + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('POST', '/server/rebuild'); + + Alert::success('A rebuild has been queued successfully. It will run the next time this server is booted.')->flash(); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.manage', $id); + } + + /** + * Manage the suspension status for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function manageSuspension(Request $request, $id) + { + $repo = new ServerRepository; + $action = $request->input('action'); + + if (! in_array($action, ['suspend', 'unsuspend'])) { + Alert::danger('Invalid action was passed to function.')->flash(); + + return redirect()->route('admin.servers.view.manage', $id); + } + + try { + $repo->toggleAccess($id, ($action === 'unsuspend')); + + Alert::success('Server has been ' . $action . 'ed.'); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attemping to ' . $action . ' this server. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.manage', $id); + } + + /** + * Update the build configuration for a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function updateBuild(Request $request, $id) + { + $repo = new ServerRepository; + + try { + $repo->changeBuild($id, $request->intersect([ + 'allocation_id', 'add_allocations', 'remove_allocations', + 'memory', 'swap', 'io', 'cpu', 'disk', + ])); - $server = new ServerRepository; - $server->changeBuild($id, [ - 'default' => $request->input('default'), - 'add_additional' => $request->input('add_additional'), - 'remove_additional' => $request->input('remove_additional'), - 'memory' => $request->input('memory'), - 'swap' => $request->input('swap'), - 'io' => $request->input('io'), - 'cpu' => $request->input('cpu'), - ]); Alert::success('Server details were successfully updated.')->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_build' - ])->withErrors(json_decode($ex->getMessage()))->withInput(); + return redirect()->route('admin.servers.view.build', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_build' - ]); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash(); + Alert::danger('An unhandled exception occured while attemping to add this server. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_build' - ]); + return redirect()->route('admin.servers.view.build', $id); } - public function deleteServer(Request $request, $id, $force = null) + /** + * Start the server deletion process. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function delete(Request $request, $id) { + $repo = new ServerRepository; + try { - $server = new ServerRepository; - $server->deleteServer($id, $force); - Alert::success('Server has been marked for deletion on the system.')->flash(); + $repo->delete($id, $request->has('force_delete')); + Alert::success('Server was successfully deleted from the system.')->flash(); + return redirect()->route('admin.servers'); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); - } catch(\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to delete this server. Please try again.')->flash(); - } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_delete' - ]); - } - - public function postToggleInstall(Request $request, $id) - { - try { - $server = new ServerRepository; - $server->toggleInstall($id); - Alert::success('Server status was successfully toggled.')->flash(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch(\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to toggle this servers status.')->flash(); - } finally { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_manage' - ]); - } - } - - public function postUpdateServerStartup(Request $request, $id) - { - try { - $server = new ServerRepository; - $server->updateStartup($id, $request->except([ - '_token' - ]), true); - Alert::success('Server startup variables were successfully updated.')->flash(); - } catch (\Pterodactyl\Exceptions\DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch(\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. Please try again.')->flash(); - } finally { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_startup' - ])->withInput(); - } - } - - public function postDatabase(Request $request, $id) - { - try { - $repo = new DatabaseRepository; - $repo->create($id, $request->except([ - '_token' - ])); - Alert::success('Added new database to this server.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_database' - ])->withInput()->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException occurred while attempting to delete this server from the daemon, please ensure it is running. This error has been logged.')->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An exception occured while attempting to add a new database for this server.')->flash(); + Alert::danger('An unhandled exception occured while attemping to delete this server. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_database' - ])->withInput(); + return redirect()->route('admin.servers.view.delete', $id); } - public function postSuspendServer(Request $request, $id) + /** + * Update the startup command as well as variables. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function saveStartup(Request $request, $id) { - try { - $repo = new ServerRepository; - $repo->suspend($id); - Alert::success('Server has been suspended on the system. All running processes have been stopped and will not be startable until it is un-suspended.'); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch(\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attemping to suspend this server. Please try again.')->flash(); - } finally { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_manage' - ]); - } - } + $repo = new ServerRepository; - public function postUnsuspendServer(Request $request, $id) - { try { - $repo = new ServerRepository; - $repo->unsuspend($id); - Alert::success('Server has been unsuspended on the system. Access has been re-enabled.'); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch(\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attemping to unsuspend this server. Please try again.')->flash(); - } finally { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_manage' - ]); - } - } + if ($repo->updateStartup($id, $request->except('_token'), true)) { + Alert::success('Service configuration successfully modfied for this server, reinstalling now.')->flash(); - public function postQueuedDeletionHandler(Request $request, $id) - { - try { - $repo = new ServerRepository; - if (!is_null($request->input('cancel'))) { - $repo->cancelDeletion($id); - Alert::success('Server deletion has been cancelled. This server will remain suspended until you unsuspend it.')->flash(); return redirect()->route('admin.servers.view', $id); - } else if(!is_null($request->input('delete'))) { - $repo->deleteNow($id); - Alert::success('Server was successfully deleted from the system.')->flash(); - return redirect()->route('admin.servers'); - } else if(!is_null($request->input('force_delete'))) { - $repo->deleteNow($id, true); - Alert::success('Server was successfully force deleted from the system.')->flash(); - return redirect()->route('admin.servers'); + } else { + Alert::success('Startup variables were successfully modified and assigned for this server.')->flash(); } + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.servers.view.startup', $id)->withErrors(json_decode($ex->getMessage())); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); - return redirect()->route('admin.servers.view', $id); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException occurred while attempting to update the startup for this server, please ensure the daemon is running. This error has been logged.')->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled error occured while attempting to perform this action.')->flash(); - return redirect()->route('admin.servers.view', $id); + Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.startup', $id); + } + + /** + * Creates a new database assigned to a specific server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function newDatabase(Request $request, $id) + { + $repo = new DatabaseRepository; + + try { + $repo->create($id, $request->only(['host', 'database', 'connection'])); + + Alert::success('A new database was assigned to this server successfully.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.servers.view.database', $id)->withInput()->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An exception occured while attempting to add a new database for this server. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.database', $id)->withInput(); + } + + /** + * Resets the database password for a specific database on this server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function resetDatabasePassword(Request $request, $id) + { + $database = Models\Database::where('server_id', $id)->findOrFail($request->input('database')); + $repo = new DatabaseRepository; + + try { + $repo->password($database->id, str_random(20)); + + return response('', 204); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json(['error' => 'A unhandled exception occurred while attempting to reset this password. This error has been logged.'], 503); } } + /** + * Deletes a database from a server. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @param int $database + * @return \Illuminate\Http\RedirectResponse + */ + public function deleteDatabase(Request $request, $id, $database) + { + $database = Models\Database::where('server_id', $id)->findOrFail($database); + $repo = new DatabaseRepository; + + try { + $repo->drop($database->id); + + return response('', 204); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json(['error' => 'A unhandled exception occurred while attempting to drop this database. This error has been logged.'], 503); + } + } } diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index dc5977470..beaac5340 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,257 +21,132 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Admin; -use Alert; -use DB; use Log; -use Validator; - +use Alert; use Pterodactyl\Models; -use Pterodactyl\Repositories\ServiceRepository; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -use Pterodactyl\Http\Controllers\Controller; use Illuminate\Http\Request; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\ServiceRepository; +use Pterodactyl\Exceptions\DisplayValidationException; class ServiceController extends Controller { - - public function __construct() - { - // - } - - public function getIndex(Request $request) + /** + * Display service overview page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function index(Request $request) { return view('admin.services.index', [ - 'services' => Models\Service::select( - 'services.*', - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.service = services.id) as c_servers') - )->get() + 'services' => Models\Service::withCount('servers', 'options', 'packs')->get(), ]); } - public function getNew(Request $request) + /** + * Display create service page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function create(Request $request) { return view('admin.services.new'); } - public function postNew(Request $request) + /** + * Return base view for a service. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function view(Request $request, $id) { + return view('admin.services.view', [ + 'service' => Models\Service::with('options', 'options.servers')->findOrFail($id), + ]); + } + + /** + * Return function editing view for a service. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewFunctions(Request $request, $id) + { + return view('admin.services.functions', ['service' => Models\Service::findOrFail($id)]); + } + + /** + * Handle post action for new service. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(Request $request) + { + $repo = new ServiceRepository; + try { - $repo = new ServiceRepository\Service; - $id = $repo->create($request->except([ - '_token' + $service = $repo->create($request->intersect([ + 'name', 'description', 'folder', 'startup', ])); Alert::success('Successfully created new service!')->flash(); - return redirect()->route('admin.services.service', $id); + + return redirect()->route('admin.services.view', $service->id); } catch (DisplayValidationException $ex) { return redirect()->route('admin.services.new')->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An error occured while attempting to add a new service.')->flash(); + Alert::danger('An error occured while attempting to add a new service. This error has been logged.')->flash(); } + return redirect()->route('admin.services.new')->withInput(); } - public function getService(Request $request, $service) + /** + * Edits configuration for a specific service. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function edit(Request $request, $id) { - return view('admin.services.view', [ - 'service' => Models\Service::findOrFail($service), - 'options' => Models\ServiceOptions::select( - 'service_options.*', - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.option = service_options.id) as c_servers') - )->where('parent_service', $service)->get() - ]); - } + $repo = new ServiceRepository; + $redirectTo = ($request->input('redirect_to')) ? 'admin.services.view.functions' : 'admin.services.view'; - public function postService(Request $request, $service) - { try { - $repo = new ServiceRepository\Service; - $repo->update($service, $request->except([ - '_token' - ])); - Alert::success('Successfully updated this service.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.service', $service)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occurred while attempting to update this service.')->flash(); - } - return redirect()->route('admin.services.service', $service)->withInput(); - } + if ($request->input('action') !== 'delete') { + $repo->update($id, $request->intersect([ + 'name', 'description', 'folder', 'startup', 'index_file', + ])); + Alert::success('Service has been updated successfully.')->flash(); + } else { + $repo->delete($id); + Alert::success('Successfully deleted service from the system.')->flash(); - public function deleteService(Request $request, $service) - { - try { - $repo = new ServiceRepository\Service; - $repo->delete($service); - Alert::success('Successfully deleted that service.')->flash(); - return redirect()->route('admin.services'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error was encountered while attempting to delete that service.')->flash(); - } - return redirect()->route('admin.services.service', $service); - } - - public function getOption(Request $request, $service, $option) - { - $opt = Models\ServiceOptions::findOrFail($option); - return view('admin.services.options.view', [ - 'service' => Models\Service::findOrFail($opt->parent_service), - 'option' => $opt, - 'variables' => Models\ServiceVariables::where('option_id', $option)->get(), - 'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail') - ->join('users', 'users.id', '=', 'servers.owner') - ->where('option', $option) - ->paginate(10) - ]); - } - - public function postOption(Request $request, $service, $option) - { - try { - $repo = new ServiceRepository\Option; - $repo->update($option, $request->except([ - '_token' - ])); - Alert::success('Option settings successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option', [$service, $option])->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to modify this option.')->flash(); - } - return redirect()->route('admin.services.option', [$service, $option])->withInput(); - } - - public function deleteOption(Request $request, $service, $option) - { - try { - $service = Models\ServiceOptions::select('parent_service')->where('id', $option)->first(); - $repo = new ServiceRepository\Option; - $repo->delete($option); - - Alert::success('Successfully deleted that option.')->flash(); - return redirect()->route('admin.services.service', $service->parent_service); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error was encountered while attempting to delete this option.')->flash(); - } - return redirect()->route('admin.services.option', [$service, $option]); - } - - public function postOptionVariable(Request $request, $service, $option, $variable) - { - try { - $repo = new ServiceRepository\Variable; - - // Because of the way old() works on the display side we prefix all of the variables with thier ID - // We need to remove that prefix here since the repo doesn't want it. - $data = [ - 'user_viewable' => '0', - 'user_editable' => '0', - 'required' => '0' - ]; - foreach($request->except(['_token']) as $id => $val) { - $data[str_replace($variable.'_', '', $id)] = $val; + return redirect()->route('admin.services'); } - $repo->update($variable, $data); - Alert::success('Successfully updated variable.')->flash(); } catch (DisplayValidationException $ex) { - $data = []; - foreach(json_decode($ex->getMessage(), true) as $id => $val) { - $data[$variable.'_'.$id] = $val; - } - return redirect()->route('admin.services.option', [$service, $option])->withErrors((object) $data)->withInput(); + return redirect()->route($redirectTo, $id)->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An error occurred while attempting to update this service.')->flash(); + Alert::danger('An error occurred while attempting to update this service. This error has been logged.')->flash(); } - return redirect()->route('admin.services.option', [$service, $option])->withInput(); - } - public function getNewVariable(Request $request, $service, $option) - { - return view('admin.services.options.variable', [ - 'service' => Models\Service::findOrFail($service), - 'option' => Models\ServiceOptions::where('parent_service', $service)->where('id', $option)->firstOrFail() - ]); + return redirect()->route($redirectTo, $id); } - - public function postNewVariable(Request $request, $service, $option) - { - try { - $repo = new ServiceRepository\Variable; - $repo->create($option, $request->except([ - '_token' - ])); - Alert::success('Successfully added new variable to this option.')->flash(); - return redirect()->route('admin.services.option', [$service, $option])->withInput(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.variable.new', [$service, $option])->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occurred while attempting to add this variable.')->flash(); - } - return redirect()->route('admin.services.option.variable.new', [$service, $option])->withInput(); - } - - public function newOption(Request $request, $service) - { - return view('admin.services.options.new', [ - 'service' => Models\Service::findOrFail($service), - ]); - } - - public function postNewOption(Request $request, $service) - { - try { - $repo = new ServiceRepository\Option; - $id = $repo->create($service, $request->except([ - '_token' - ])); - Alert::success('Successfully created new service option.')->flash(); - return redirect()->route('admin.services.option', [$service, $id]); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.new', $service)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add this service option.')->flash(); - } - return redirect()->route('admin.services.option.new', $service)->withInput(); - } - - public function deleteVariable(Request $request, $service, $option, $variable) - { - try { - $repo = new ServiceRepository\Variable; - $repo->delete($variable); - Alert::success('Deleted variable.')->flash(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to delete that variable.')->flash(); - } - return redirect()->route('admin.services.option', [$service, $option]); - } - } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 18ec6f1a1..0e943eb46 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -1,8 +1,8 @@ - * Some Modifications (c) 2015 Dylan Seidt + * Copyright (c) 2015 - 2017 Dane Everitt + * Some Modifications (c) 2015 Dylan Seidt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,120 +22,159 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Admin; -use Alert; -use Settings; -use Mail; use Log; -use Pterodactyl\Models\User; -use Pterodactyl\Repositories\UserRepository; -use Pterodactyl\Models\Server; - -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -use Pterodactyl\Http\Controllers\Controller; +use Alert; use Illuminate\Http\Request; +use Pterodactyl\Models\User; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\UserRepository; +use Pterodactyl\Exceptions\DisplayValidationException; class UserController extends Controller { - /** - * Controller Constructor + * Display user index page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ - public function __construct() + public function index(Request $request) { - // - } + $users = User::withCount('servers', 'subuserOf'); + + if (! is_null($request->input('query'))) { + $users->search($request->input('query')); + } - public function getIndex(Request $request) - { return view('admin.users.index', [ - 'users' => User::paginate(20) + 'users' => $users->paginate(25), ]); } - public function getNew(Request $request) + /** + * Display new user page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function create(Request $request) { return view('admin.users.new'); } - public function getView(Request $request, $id) + /** + * Display user view page. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function view(Request $request, $id) { return view('admin.users.view', [ - 'user' => User::findOrFail($id), - 'servers' => Server::select('servers.*', 'nodes.name as nodeName', 'locations.long as location') - ->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('locations', 'nodes.location', '=', 'locations.id') - ->where('owner', $id) - ->get(), + 'user' => User::with('servers.node')->findOrFail($id), ]); } - public function deleteUser(Request $request, $id) + /** + * Delete a user. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function delete(Request $request, $id) { try { $repo = new UserRepository; $repo->delete($id); Alert::success('Successfully deleted user from system.')->flash(); + return redirect()->route('admin.users'); - } catch(DisplayException $ex) { + } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); Alert::danger('An exception was encountered while attempting to delete this user.')->flash(); } + return redirect()->route('admin.users.view', $id); } - public function postNew(Request $request) + /** + * Create a user. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(Request $request) { try { $user = new UserRepository; - $userid = $user->create($request->input('email'), $request->input('password')); + $userid = $user->create($request->only([ + 'email', 'password', 'name_first', + 'name_last', 'username', 'root_admin', + ])); Alert::success('Account has been successfully created.')->flash(); + return redirect()->route('admin.users.view', $userid); } catch (DisplayValidationException $ex) { return redirect()->route('admin.users.new')->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (\Exception $ex) { Log::error($ex); Alert::danger('An error occured while attempting to add a new user.')->flash(); + return redirect()->route('admin.users.new'); } } - public function updateUser(Request $request, $user) + /** + * Update a user. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function update(Request $request, $id) { - $data = [ - 'email' => $request->input('email'), - 'root_admin' => $request->input('root_admin'), - 'password_confirmation' => $request->input('password_confirmation'), - ]; - - if ($request->input('password')) { - $data['password'] = $request->input('password'); - } - try { $repo = new UserRepository; - $repo->update($user, $data); + $user = $repo->update($id, array_merge( + $request->only('root_admin'), + $request->intersect([ + 'email', 'password', 'name_first', + 'name_last', 'username', + ]) + )); Alert::success('User account was successfully updated.')->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $e) { - Log::error($e); + return redirect()->route('admin.users.view', $id)->withErrors(json_decode($ex->getMessage())); + } catch (\Exception $ex) { + Log::error($ex); Alert::danger('An error occured while attempting to update this user.')->flash(); } - return redirect()->route('admin.users.view', $user); + + return redirect()->route('admin.users.view', $id); } - public function getJson(Request $request) + /** + * Get a JSON response of users on the system. + * + * @param \Illuminate\Http\Request $request + * @return \Pterodactyl\Models\User + */ + public function json(Request $request) { - foreach(User::select('email')->get() as $user) { - $resp[] = $user->email; - } - return $resp; - } + return User::select('id', 'email', 'username', 'name_first', 'name_last') + ->search($request->input('q')) + ->get()->transform(function ($item) { + $item->md5 = md5(strtolower($item->email)); + return $item; + }); + } } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 6d5f4bfa1..9dd80824a 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -1,8 +1,33 @@ . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ namespace Pterodactyl\Http\Controllers\Auth; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Password; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Events\Auth\FailedPasswordReset; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; class ForgotPasswordController extends Controller @@ -29,4 +54,21 @@ class ForgotPasswordController extends Controller { $this->middleware('guest'); } + + /** + * Get the response for a failed password reset link. + * + * @param \Illuminate\Http\Request + * @param string $response + * @return \Illuminate\Http\RedirectResponse + */ + protected function sendResetLinkFailedResponse(Request $request, $response) + { + // As noted in #358 we will return success even if it failed + // to avoid pointing out that an account does or does not + // exist on the system. + event(new FailedPasswordReset($request->ip(), $request->only('email'))); + + return $this->sendResetLinkResponse(Password::RESET_LINK_SENT); + } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 197e69592..e4ca0d2ca 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -1,8 +1,8 @@ - * Some Modifications (c) 2015 Dylan Seidt + * Copyright (c) 2015 - 2017 Dane Everitt + * Some Modifications (c) 2015 Dylan Seidt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,20 +22,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Auth; -use Pterodactyl\Models\User; - use Auth; -use Alert; -use Validator; - -use Pterodactyl\Http\Controllers\Controller; -use PragmaRX\Google2FA\Google2FA; +use Cache; +use Crypt; use Illuminate\Http\Request; -use Illuminate\Foundation\Auth\ThrottlesLogins; -use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; - +use Pterodactyl\Models\User; +use PragmaRX\Google2FA\Google2FA; +use Pterodactyl\Http\Controllers\Controller; use Illuminate\Foundation\Auth\AuthenticatesUsers; class LoginController extends Controller @@ -62,14 +58,14 @@ class LoginController extends Controller /** * Lockout time for failed login requests. * - * @var integer + * @var int */ protected $lockoutTime = 120; /** * After how many attempts should logins be throttled and locked. * - * @var integer + * @var int */ protected $maxLoginAttempts = 3; @@ -84,70 +80,153 @@ class LoginController extends Controller } /** - * Handle a login request to the application. + * Get the failed login response instance. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function login(Request $request) + protected function sendFailedLoginResponse(Request $request) { + $this->incrementLoginAttempts($request); - $this->validate($request, [ - 'email' => 'required|email', - 'password' => 'required', - ]); + $errors = [$this->username() => trans('auth.failed')]; - if ($lockedOut = $this->hasTooManyLoginAttempts($request)) { - $this->fireLockoutEvent($request); - return $this->sendLockoutResponse($request); + if ($request->expectsJson()) { + return response()->json($errors, 422); } - // Is the email & password valid? - if (!Auth::attempt([ - 'email' => $request->input('email'), - 'password' => $request->input('password') - ], $request->has('remember'))) { - - if (!$lockedOut) { - $this->incrementLoginAttempts($request); - } - - return $this->sendFailedLoginResponse($request); - - } - - $G2FA = new Google2FA(); - $user = User::select('use_totp', 'totp_secret')->where('email', $request->input('email'))->first(); - - // Verify TOTP Token was Valid - if($user->use_totp === 1) { - if(!$G2FA->verifyKey($user->totp_secret, $request->input('totp_token'))) { - - Auth::logout(); - - if (!$lockedOut) { - $this->incrementLoginAttempts($request); - } - - Alert::danger(trans('auth.totp_failed'))->flash(); - return $this->sendFailedLoginResponse($request); - - } - } - - return $this->sendLoginResponse($request); - + return redirect()->route('auth.login') + ->withInput($request->only($this->username(), 'remember')) + ->withErrors($errors); } /** - * Check if the provided user has TOTP enabled. + * Handle a login request to the application. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\Response|\Illuminate\Response\RedirectResponse */ - public function checkTotp(Request $request) + public function login(Request $request) { - return response()->json(User::select('id')->where('email', $request->input('email'))->where('use_totp', 1)->first()); + // Check wether the user identifier is an email address or a username + $checkField = str_contains($request->input('user'), '@') ? 'email' : 'username'; + + if ($this->hasTooManyLoginAttempts($request)) { + $this->fireLockoutEvent($request); + + return $this->sendLockoutResponse($request); + } + + // Determine if the user even exists. + $user = User::where($checkField, $request->input($this->username()))->first(); + if (! $user) { + return $this->sendFailedLoginResponse($request); + } + + // If user uses 2FA, redirect to that page. + if ($user->use_totp) { + $token = str_random(64); + Cache::put($token, [ + 'user_id' => $user->id, + 'credentials' => Crypt::encrypt(serialize([ + $checkField => $request->input($this->username()), + 'password' => $request->input('password'), + ])), + ], 5); + + return redirect()->route('auth.totp') + ->with('authentication_token', $token) + ->with('remember', $request->has('remember')); + } + + $attempt = Auth::attempt([ + $checkField => $request->input($this->username()), + 'password' => $request->input('password'), + 'use_totp' => 0, + ], $request->has('remember')); + + if ($attempt) { + return $this->sendLoginResponse($request); + } + + // Login failed, send response. + return $this->sendFailedLoginResponse($request); } + /** + * Handle a TOTP implementation page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function totp(Request $request) + { + $token = $request->session()->get('authentication_token'); + + if (is_null($token) || Auth::user()) { + return redirect()->route('auth.login'); + } + + return view('auth.totp', [ + 'verify_key' => $token, + 'remember' => $request->session()->get('remember'), + ]); + } + + /** + * Handle a TOTP input. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function totpCheckpoint(Request $request) + { + $G2FA = new Google2FA(); + + if (is_null($request->input('verify_token'))) { + return $this->sendFailedLoginResponse($request); + } + + $cache = Cache::pull($request->input('verify_token')); + $user = User::where('id', $cache['user_id'])->first(); + + if (! $user || ! $cache) { + $this->sendFailedLoginResponse($request); + } + + if (is_null($request->input('2fa_token'))) { + return $this->sendFailedLoginResponse($request); + } + + try { + $credentials = unserialize(Crypt::decrypt($cache['credentials'])); + } catch (\Illuminate\Contracts\Encryption\DecryptException $ex) { + return $this->sendFailedLoginResponse($request); + } + + if (! $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'), 2)) { + event(new \Illuminate\Auth\Events\Failed($user, $credentials)); + + return $this->sendFailedLoginResponse($request); + } + + $attempt = Auth::attempt($credentials, $request->has('remember')); + + if ($attempt) { + return $this->sendLoginResponse($request); + } + + // Login failed, send response. + return $this->sendFailedLoginResponse($request); + } + + /** + * Get the login username to be used by the controller. + * + * @return string + */ + public function username() + { + return 'user'; + } } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index e2663825a..d28692293 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Http\Controllers\Auth; -use Pterodactyl\User; use Validator; +use Pterodactyl\User; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Foundation\Auth\RegistersUsers; diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index e9989ec83..628f55f42 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -21,6 +21,11 @@ class ResetPasswordController extends Controller use ResetsPasswords; + /** + * The URL to redirect users to after password reset. + * + * @var string + */ public $redirectTo = '/'; /** @@ -33,10 +38,16 @@ class ResetPasswordController extends Controller $this->middleware('guest'); } - - protected function rules() { + /** + * Return the rules used when validating password reset. + * + * @return array + */ + protected function rules() + { return [ - 'token' => 'required', 'email' => 'required|email', + 'token' => 'required', + 'email' => 'required|email', 'password' => 'required|confirmed|' . User::PASSWORD_RULES, ]; } diff --git a/app/Http/Controllers/Base/APIController.php b/app/Http/Controllers/Base/APIController.php index da9a20d78..9c3816db3 100644 --- a/app/Http/Controllers/Base/APIController.php +++ b/app/Http/Controllers/Base/APIController.php @@ -1,8 +1,8 @@ - * Some Modifications (c) 2015 Dylan Seidt + * Copyright (c) 2015 - 2017 Dane Everitt + * Some Modifications (c) 2015 Dylan Seidt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,45 +22,66 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Base; -use Alert; use Log; - -use Pterodactyl\Models; - +use Alert; +use Illuminate\Http\Request; +use Pterodactyl\Models\APIKey; +use Pterodactyl\Models\APIPermission; use Pterodactyl\Repositories\APIRepository; -use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; - -use Illuminate\Http\Request; +use Pterodactyl\Exceptions\DisplayValidationException; class APIController extends Controller { + /** + * Display base API index page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ public function index(Request $request) { - $keys = Models\APIKey::where('user', $request->user()->id)->get(); - foreach($keys as &$key) { - $key->permissions = Models\APIPermission::where('key_id', $key->id)->get(); - } - return view('base.api.index', [ - 'keys' => $keys + 'keys' => APIKey::where('user_id', $request->user()->id)->get(), ]); } - public function new(Request $request) + /** + * Display API key creation page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function create(Request $request) { - return view('base.api.new'); + return view('base.api.new', [ + 'permissions' => [ + 'user' => collect(APIPermission::permissions())->pull('_user'), + 'admin' => collect(APIPermission::permissions())->except('_user')->toArray(), + ], + ]); } - public function save(Request $request) + /** + * Handle saving new API key. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(Request $request) { try { $repo = new APIRepository($request->user()); - $secret = $repo->new($request->except(['_token'])); - Alert::success('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

' . $secret . '')->flash(); + $secret = $repo->create($request->intersect([ + 'memo', 'allowed_ips', + 'admin_permissions', 'permissions', + ])); + Alert::success('An API Key-Pair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

' . $secret . '')->flash(); + return redirect()->route('account.api'); } catch (DisplayValidationException $ex) { return redirect()->route('account.api.new')->withErrors(json_decode($ex->getMessage()))->withInput(); @@ -70,18 +91,29 @@ class APIController extends Controller Log::error($ex); Alert::danger('An unhandled exception occured while attempting to add this API key.')->flash(); } + return redirect()->route('account.api.new')->withInput(); } + /** + * Handle revoking API key. + * + * @param \Illuminate\Http\Request $request + * @param string $key + * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response + */ public function revoke(Request $request, $key) { try { $repo = new APIRepository($request->user()); $repo->revoke($key); + return response('', 204); } catch (\Exception $ex) { + Log::error($ex); + return response()->json([ - 'error' => 'An error occured while attempting to remove this key.' + 'error' => 'An error occured while attempting to remove this key.', ], 503); } } diff --git a/app/Http/Controllers/Base/AccountController.php b/app/Http/Controllers/Base/AccountController.php index a58c691a8..10c33e380 100644 --- a/app/Http/Controllers/Base/AccountController.php +++ b/app/Http/Controllers/Base/AccountController.php @@ -1,8 +1,8 @@ - * Some Modifications (c) 2015 Dylan Seidt + * Copyright (c) 2015 - 2017 Dane Everitt + * Some Modifications (c) 2015 Dylan Seidt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,23 +22,24 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Base; +use Log; use Alert; - -use Pterodactyl\Models\User; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; - use Illuminate\Http\Request; +use Pterodactyl\Models\User; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\UserRepository; +use Pterodactyl\Exceptions\DisplayValidationException; class AccountController extends Controller { /** * Display base account information page. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ public function index(Request $request) { @@ -46,64 +47,59 @@ class AccountController extends Controller } /** - * Update an account email. + * Update details for a users account. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ - public function email(Request $request) + public function update(Request $request) { + $data = []; - $this->validate($request, [ - 'new_email' => 'required|email', - 'password' => 'required' - ]); + // Request to update account Password + if ($request->input('do_action') === 'password') { + $this->validate($request, [ + 'current_password' => 'required', + 'new_password' => 'required|confirmed|' . User::PASSWORD_RULES, + 'new_password_confirmation' => 'required', + ]); - $user = $request->user(); + $data['password'] = $request->input('new_password'); - if (!password_verify($request->input('password'), $user->password)) { - Alert::danger('The password provided was not valid for this account.')->flash(); - return redirect()->route('account'); + // Request to update account Email + } elseif ($request->input('do_action') === 'email') { + $data['email'] = $request->input('new_email'); + + // Request to update account Identity + } elseif ($request->input('do_action') === 'identity') { + $data = $request->only(['name_first', 'name_last', 'username']); + + // Unknown, hit em with a 404 + } else { + return abort(404); } - $user->email = $request->input('new_email'); - $user->save(); + if ( + in_array($request->input('do_action'), ['email', 'password']) + && ! password_verify($request->input('current_password'), $request->user()->password) + ) { + Alert::danger(trans('base.account.invalid_pass'))->flash(); - Alert::success('Your email address has successfully been updated.')->flash(); - return redirect()->route('account'); - - } - - /** - * Update an account password. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function password(Request $request) - { - - $this->validate($request, [ - 'current_password' => 'required', - 'new_password' => 'required|confirmed|different:current_password|' . User::PASSWORD_RULES, - 'new_password_confirmation' => 'required' - ]); - - $user = $request->user(); - - if (!password_verify($request->input('current_password'), $user->password)) { - Alert::danger('The password provided was not valid for this account.')->flash(); return redirect()->route('account'); } try { - $user->setPassword($request->input('new_password')); - Alert::success('Your password has successfully been updated.')->flash(); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); + $repo = new UserRepository; + $repo->update($request->user()->id, $data); + Alert::success('Your account details were successfully updated.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('account')->withErrors(json_decode($ex->getMessage())); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger(trans('base.account.exception'))->flash(); } return redirect()->route('account'); - } } diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index bed1cb988..556ea157c 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -1,8 +1,8 @@ - * Some Modifications (c) 2015 Dylan Seidt + * Copyright (c) 2015 - 2017 Dane Everitt + * Some Modifications (c) 2015 Dylan Seidt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,47 +22,89 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Base; +use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Http\Request; - class IndexController extends Controller { - - /** - * Controller Constructor - */ - public function __construct() - { - // - } - /** * Returns listing of user's servers. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ public function getIndex(Request $request) { + $servers = $request->user()->access()->with('user'); + + if (! is_null($request->input('query'))) { + $servers->search($request->input('query')); + } + return view('base.index', [ - 'servers' => Server::getUserServers(10), + 'servers' => $servers->paginate(config('pterodactyl.paginate.frontend.servers')), ]); } /** * Generate a random string. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request + * @param int $length * @return string + * @deprecated */ public function getPassword(Request $request, $length = 16) { $length = ($length < 8) ? 8 : $length; - return str_random($length); + + $returnable = false; + while (! $returnable) { + $generated = str_random($length); + if (preg_match('/[A-Z]+[a-z]+[0-9]+/', $generated)) { + $returnable = true; + } + } + + return $generated; } + /** + * Returns status of the server in a JSON response used for populating active status list. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + */ + public function status(Request $request, $uuid) + { + $server = Server::byUuid($uuid); + + if (! $server) { + return response()->json([], 404); + } + + if (! $server->installed) { + return response()->json(['status' => 20]); + } + + if ($server->suspended) { + return response()->json(['status' => 30]); + } + + try { + $res = $server->guzzleClient()->request('GET', '/server'); + if ($res->getStatusCode() === 200) { + return response()->json(json_decode($res->getBody())); + } + } catch (\Exception $e) { + // + } + + return response()->json([]); + } } diff --git a/app/Http/Controllers/Base/LanguageController.php b/app/Http/Controllers/Base/LanguageController.php index c4afc1e50..67d04f66c 100644 --- a/app/Http/Controllers/Base/LanguageController.php +++ b/app/Http/Controllers/Base/LanguageController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,39 +21,40 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Base; use Auth; use Session; - -use Pterodactyl\Models\User; use Illuminate\Http\Request; +use Pterodactyl\Models\User; use Pterodactyl\Http\Controllers\Controller; class LanguageController extends Controller { - + /** + * A list of supported languages on the panel. + * + * @var array + */ protected $languages = [ - 'de' => 'Danish', + 'de' => 'German', 'en' => 'English', - 'es' => 'Spanish', - 'fr' => 'French', - 'it' => 'Italian', - 'pl' => 'Polish', + 'et' => 'Estonian', + 'nb' => 'Norwegian', + 'nl' => 'Dutch', 'pt' => 'Portuguese', + 'ro' => 'Romanian', 'ru' => 'Russian', - 'se' => 'Swedish', - 'zh' => 'Chinese', ]; /** - * Controller Constructor + * Sets the language for a user. + * + * @param \Illuminate\Http\Request $request + * @param string $language + * @return \Illuminate\Http\RedirectResponse */ - public function __construct() - { - // - } - public function setLanguage(Request $request, $language) { if (array_key_exists($language, $this->languages)) { @@ -62,9 +63,9 @@ class LanguageController extends Controller $user->language = $language; $user->save(); } - Session::set('applocale', $language); + Session::put('applocale', $language); } + return redirect()->back(); } - } diff --git a/app/Http/Controllers/Base/SecurityController.php b/app/Http/Controllers/Base/SecurityController.php index dec2f3fd1..052a7a527 100644 --- a/app/Http/Controllers/Base/SecurityController.php +++ b/app/Http/Controllers/Base/SecurityController.php @@ -1,8 +1,8 @@ - * Some Modifications (c) 2015 Dylan Seidt + * Copyright (c) 2015 - 2017 Dane Everitt + * Some Modifications (c) 2015 Dylan Seidt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,29 +22,27 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Base; -use Google2FA; use Alert; - +use Google2FA; +use Illuminate\Http\Request; use Pterodactyl\Models\Session; use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Http\Request; - class SecurityController extends Controller { - /** * Returns Security Management Page. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ public function index(Request $request) { return view('base.security', [ - 'sessions' => Session::where('user_id', $request->user()->id)->get() + 'sessions' => Session::where('user_id', $request->user()->id)->get(), ]); } @@ -52,12 +50,11 @@ class SecurityController extends Controller * Generates TOTP Secret and returns popup data for user to verify * that they can generate a valid response. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse */ public function generateTotp(Request $request) { - $user = $request->user(); $user->totp_secret = Google2FA::generateSecretKey(); @@ -69,62 +66,67 @@ class SecurityController extends Controller $user->email, $user->totp_secret ), - 'secret' => $user->totp_secret + 'secret' => $user->totp_secret, ]); - } /** * Verifies that 2FA token recieved is valid and will work on the account. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function setTotp(Request $request) { - - if (!$request->has('token')) { - return response(null, 500); + if (! $request->has('token')) { + return response()->json([ + 'error' => 'Request is missing token parameter.', + ], 500); } $user = $request->user(); - if($user->toggleTotp($request->input('token'))) { + if ($user->toggleTotp($request->input('token'))) { return response('true'); } return response('false'); - } /** * Disables TOTP on an account. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse */ public function disableTotp(Request $request) { - - if (!$request->has('token')) { + if (! $request->has('token')) { Alert::danger('Missing required `token` field in request.')->flash(); - return redirect()->route('account.totp'); + + return redirect()->route('account.security'); } $user = $request->user(); - if($user->toggleTotp($request->input('token'))) { + if ($user->toggleTotp($request->input('token'))) { return redirect()->route('account.security'); } Alert::danger('The TOTP token provided was invalid.')->flash(); - return redirect()->route('account.security'); + return redirect()->route('account.security'); } + /** + * Revokes a user session. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ public function revoke(Request $request, $id) { - $session = Session::where('id', $id)->where('user_id', $request->user()->id)->firstOrFail(); - $session->delete(); + Session::where('user_id', $request->user()->id)->findOrFail($id)->delete(); + return redirect()->route('account.security'); } - } diff --git a/app/Http/Controllers/Daemon/ActionController.php b/app/Http/Controllers/Daemon/ActionController.php new file mode 100644 index 000000000..0a054218c --- /dev/null +++ b/app/Http/Controllers/Daemon/ActionController.php @@ -0,0 +1,106 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Daemon; + +use Cache; +use Illuminate\Http\Request; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Server; +use Pterodactyl\Http\Controllers\Controller; + +class ActionController extends Controller +{ + /** + * Handles download request from daemon. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function authenticateDownload(Request $request) + { + $download = Cache::tags(['Server:Downloads'])->pull($request->input('token')); + + if (is_null($download)) { + return response()->json([ + 'error' => 'An invalid request token was recieved with this request.', + ], 403); + } + + return response()->json([ + 'path' => $download['path'], + 'server' => $download['server'], + ]); + } + + /** + * Handles install toggle request from daemon. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function markInstall(Request $request) + { + $server = Server::where('uuid', $request->input('server'))->with('node')->first(); + if (! $server) { + return response()->json([ + 'error' => 'No server by that ID was found on the system.', + ], 422); + } + + $hmac = $request->input('signed'); + $status = $request->input('installed'); + + if (! hash_equals(base64_decode($hmac), hash_hmac('sha256', $server->uuid, $server->node->daemonSecret, true))) { + return response()->json([ + 'error' => 'Signed HMAC was invalid.', + ], 403); + } + + $server->installed = ($status === 'installed') ? 1 : 2; + $server->save(); + + return response()->json([]); + } + + /** + * Handles configuration data request from daemon. + * + * @param \Illuminate\Http\Request $request + * @param string $token + * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response + */ + public function configuration(Request $request, $token) + { + $nodeId = Cache::tags(['Node:Configuration'])->pull($token); + if (is_null($nodeId)) { + return response()->json(['error' => 'token_invalid'], 403); + } + + $node = Node::findOrFail($nodeId); + + // Manually as getConfigurationAsJson() returns it in correct format already + return response($node->getConfigurationAsJson())->header('Content-Type', 'text/json'); + } +} diff --git a/app/Http/Controllers/Daemon/OptionController.php b/app/Http/Controllers/Daemon/OptionController.php new file mode 100644 index 000000000..9eb1806dc --- /dev/null +++ b/app/Http/Controllers/Daemon/OptionController.php @@ -0,0 +1,64 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Daemon; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Server; +use Pterodactyl\Http\Controllers\Controller; + +class OptionController extends Controller +{ + public function details(Request $request, $server) + { + $server = Server::with('allocation', 'option', 'variables.variable')->where('uuid', $server)->firstOrFail(); + + $environment = $server->variables->map(function ($item) { + return sprintf('%s=%s', $item->variable->env_variable, $item->variable_value); + }); + + $mergeInto = [ + 'STARTUP=' . $server->startup, + 'SERVER_MEMORY=' . $server->memory, + 'SERVER_IP=' . $server->allocation->ip, + 'SERVER_PORT=' . $server->allocation->port, + ]; + + if ($environment->count() === 0) { + $environment = collect($mergeInto); + } + + return response()->json([ + 'scripts' => [ + 'install' => (! $server->option->copy_script_install) ? null : str_replace(["\r\n", "\n", "\r"], "\n", $server->option->copy_script_install), + 'privileged' => $server->option->script_is_privileged, + ], + 'config' => [ + 'container' => $server->option->copy_script_container, + 'entry' => $server->option->copy_script_entry, + ], + 'env' => $environment->toArray(), + ]); + } +} diff --git a/app/Http/Controllers/Daemon/PackController.php b/app/Http/Controllers/Daemon/PackController.php new file mode 100644 index 000000000..419ae7c9c --- /dev/null +++ b/app/Http/Controllers/Daemon/PackController.php @@ -0,0 +1,90 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Daemon; + +use Storage; +use Pterodactyl\Models; +use Illuminate\Http\Request; +use Pterodactyl\Http\Controllers\Controller; + +class PackController extends Controller +{ + /** + * Pulls an install pack archive from the system. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public function pull(Request $request, $uuid) + { + $pack = Models\Pack::where('uuid', $uuid)->first(); + + if (! $pack) { + return response()->json(['error' => 'No such pack.'], 404); + } + + if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) { + return response()->json(['error' => 'There is no archive available for this pack.'], 503); + } + + return response()->download(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz')); + } + + /** + * Returns the hash information for a pack. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + */ + public function hash(Request $request, $uuid) + { + $pack = Models\Pack::where('uuid', $uuid)->first(); + + if (! $pack) { + return response()->json(['error' => 'No such pack.'], 404); + } + + if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) { + return response()->json(['error' => 'There is no archive available for this pack.'], 503); + } + + return response()->json([ + 'archive.tar.gz' => sha1_file(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz')), + ]); + } + + /** + * Pulls an update pack archive from the system. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + public function pullUpdate(Request $request) + { + // + } +} diff --git a/app/Http/Controllers/Daemon/ServiceController.php b/app/Http/Controllers/Daemon/ServiceController.php new file mode 100644 index 000000000..d461786f0 --- /dev/null +++ b/app/Http/Controllers/Daemon/ServiceController.php @@ -0,0 +1,100 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Daemon; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Service; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Http\Controllers\Controller; + +class ServiceController extends Controller +{ + /** + * Returns a listing of all services currently on the system, + * as well as the associated files and the file hashes for + * caching purposes. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function listServices(Request $request) + { + $response = []; + foreach (Service::all() as $service) { + $response[$service->folder] = [ + 'main.json' => sha1($this->getConfiguration($service->id)->toJson()), + 'index.js' => sha1($service->index_file), + ]; + } + + return response()->json($response); + } + + /** + * Returns the contents of the requested file for the given service. + * + * @param \Illuminate\Http\Request $request + * @param string $folder + * @param string $file + * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\FileResponse + */ + public function pull(Request $request, $folder, $file) + { + $service = Service::where('folder', $folder)->firstOrFail(); + + if ($file === 'index.js') { + return response($service->index_file)->header('Content-Type', 'text/plain'); + } elseif ($file === 'main.json') { + return response()->json($this->getConfiguration($service->id)); + } + + return abort(404); + } + + /** + * Returns a `main.json` file based on the configuration + * of each service option. + * + * @param int $id + * @return \Illuminate\Support\Collection + */ + protected function getConfiguration($id) + { + $options = ServiceOption::where('service_id', $id)->get(); + + return $options->mapWithKeys(function ($item) use ($options) { + return [ + $item->tag => array_filter([ + 'symlink' => $options->where('id', $item->config_from)->pluck('tag')->pop(), + 'startup' => json_decode($item->config_startup), + 'stop' => $item->config_stop, + 'configs' => json_decode($item->config_files), + 'log' => json_decode($item->config_logs), + 'query' => 'none', + ]), + ]; + }); + } +} diff --git a/app/Http/Controllers/Remote/RemoteController.php b/app/Http/Controllers/Remote/RemoteController.php deleted file mode 100644 index ee1b86b4c..000000000 --- a/app/Http/Controllers/Remote/RemoteController.php +++ /dev/null @@ -1,111 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Controllers\Remote; - -use Pterodactyl\Models; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Services\NotificationService; - -use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Http\Request; - -class RemoteController extends Controller -{ - - /** - * Controller Constructor - */ - public function __construct() - { - // No middleware for this route. - } - - public function postDownload(Request $request) { - $download = Models\Download::where('token', $request->input('token', '00'))->first(); - if (!$download) { - return response()->json([ - 'error' => 'An invalid request token was recieved with this request.' - ], 403); - } - - $download->delete(); - return response()->json([ - 'path' => $download->path, - 'server' => $download->server - ]); - } - - public function postInstall(Request $request) - { - $server = Models\Server::where('uuid', $request->input('server'))->first(); - if (!$server) { - return response()->json([ - 'error' => 'No server by that ID was found on the system.' - ], 422); - } - - $node = Models\Node::findOrFail($server->node); - $hmac = $request->input('signed'); - $status = $request->input('installed'); - - if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $node->daemonSecret, true)) { - return response()->json([ - 'error' => 'Signed HMAC was invalid.' - ], 403); - } - - $server->installed = ($status === 'installed') ? 1 : 2; - $server->save(); - - return response()->json([ - 'message' => 'Recieved!' - ], 200); - } - - public function event(Request $request) - { - $server = Models\Server::where('uuid', $request->input('server'))->first(); - if (!$server) { - return response()->json([ - 'error' => 'No server by that ID was found on the system.' - ], 422); - } - - $node = Models\Node::findOrFail($server->node); - - $hmac = $request->input('signed'); - if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $node->daemonSecret, true)) { - return response()->json([ - 'error' => 'Signed HMAC was invalid.' - ], 403); - } - - // Passes Validation, Setup Notifications - $notify = new NotificationService($server); - $notify->pass($request->input('notification')); - - return response('', 201); - } - -} diff --git a/app/Http/Controllers/Server/AjaxController.php b/app/Http/Controllers/Server/AjaxController.php index f2db322ec..c22b0c202 100644 --- a/app/Http/Controllers/Server/AjaxController.php +++ b/app/Http/Controllers/Server/AjaxController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,24 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Server; use Log; use Pterodactyl\Models; - -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -use Pterodactyl\Repositories; -use Pterodactyl\Http\Controllers\Controller; use Illuminate\Http\Request; - -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Repositories; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Exceptions\DisplayValidationException; class AjaxController extends Controller { - /** * @var array */ @@ -54,60 +49,21 @@ class AjaxController extends Controller */ protected $directory; - /** - * Controller Constructor - */ - public function __construct() - { - // - } - - /** - * Returns true or false depending on the power status of the requested server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Contracts\View\View - */ - public function getStatus(Request $request, $uuid) - { - $server = Models\Server::getByUUID($uuid); - - if (!$server) { - return response()->json([], 404); - } - - $client = Models\Node::guzzleRequest($server->node); - - try { - $res = $client->request('GET', '/server', [ - 'headers' => Models\Server::getGuzzleHeaders($uuid) - ]); - if($res->getStatusCode() === 200) { - return response()->json(json_decode($res->getBody())); - } - } catch (RequestException $e) { - // - } - return response()->json([]); - } - /** * Returns a listing of files in a given directory for a server. * - * @param \Illuminate\Http\Request $request - * @param string $uuid` - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View|\Illuminate\Http\Response */ public function postDirectoryList(Request $request, $uuid) { - - $server = Models\Server::getByUUID($uuid); - $this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/'); + $server = Models\Server::byUuid($uuid); $this->authorize('list-files', $server); + $this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/'); $prevDir = [ - 'header' => ($this->directory !== '/') ? $this->directory : '' + 'header' => ($this->directory !== '/') ? $this->directory : '', ]; if ($this->directory !== '/') { $prevDir['first'] = true; @@ -116,7 +72,7 @@ class AjaxController extends Controller // Determine if we should show back links in the file browser. // This code is strange, and could probably be rewritten much better. $goBack = explode('/', trim($this->directory, '/')); - if (!empty(array_filter($goBack)) && count($goBack) >= 2) { + if (! empty(array_filter($goBack)) && count($goBack) >= 2) { $prevDir['show'] = true; array_pop($goBack); $prevDir['link'] = '/' . implode('/', $goBack); @@ -131,6 +87,7 @@ class AjaxController extends Controller return response($ex->getMessage(), 500); } catch (\Exception $ex) { Log::error($ex); + return response('An error occured while attempting to load the requested directory, please try again.', 500); } @@ -139,61 +96,61 @@ class AjaxController extends Controller 'files' => $directoryContents->files, 'folders' => $directoryContents->folders, 'editableMime' => Repositories\HelperRepository::editableFiles(), - 'directory' => $prevDir + 'directory' => $prevDir, ]); - } /** * Handles a POST request to save a file. * - * @param Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\Response */ public function postSaveFile(Request $request, $uuid) { - - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('save-files', $server); $controller = new Repositories\Daemon\FileRepository($uuid); try { $controller->saveFileContents($request->input('file'), $request->input('contents')); + return response(null, 204); } catch (DisplayException $ex) { return response($ex->getMessage(), 500); } catch (\Exception $ex) { Log::error($ex); + return response('An error occured while attempting to save this file, please try again.', 500); } - } /** - * [postSetPrimary description] - * @param Request $request - * @param string $uuid - * @return \Illuminate\Http\Response + * Sets the primary allocation for a server. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * @deprecated */ public function postSetPrimary(Request $request, $uuid) { - - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('allocations'); $this->authorize('set-connection', $server); - if ((int) $request->input('allocation') === $server->allocation) { + if ((int) $request->input('allocation') === $server->allocation_id) { return response()->json([ - 'error' => 'You are already using this as your default connection.' + 'error' => 'You are already using this as your default connection.', ], 409); } try { - $allocation = Models\Allocation::where('id', $request->input('allocation'))->where('assigned_to', $server->id)->first(); - if (!$allocation) { + $allocation = $server->allocations->where('id', $request->input('allocation'))->where('server_id', $server->id)->first(); + if (! $allocation) { return response()->json([ - 'error' => 'No allocation matching your request was found in the system.' + 'error' => 'No allocation matching your request was found in the system.', ], 422); } @@ -201,6 +158,7 @@ class AjaxController extends Controller $repo->changeBuild($server->id, [ 'default' => $allocation->ip . ':' . $allocation->port, ]); + return response('The default connection for this server has been updated. Please be aware that you will need to restart your server for this change to go into effect.'); } catch (DisplayValidationException $ex) { return response()->json([ @@ -212,34 +170,42 @@ class AjaxController extends Controller ], 503); } catch (\Exception $ex) { Log::error($ex); + return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify the default connection for this server.' + 'error' => 'An unhandled exception occured while attemping to modify the default connection for this server.', ], 503); } } + /** + * Resets a database password for a server. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * @deprecated + */ public function postResetDatabasePassword(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); - $database = Models\Database::where('id', $request->input('database'))->where('server_id', $server->id)->firstOrFail(); - + $server = Models\Server::byUuid($uuid); $this->authorize('reset-db-password', $server); - try { - $repo = new Repositories\DatabaseRepository; - $password = str_random(16); - $repo->modifyPassword($request->input('database'), $password); + $database = Models\Database::where('server_id', $server->id)->findOrFail($request->input('database')); + $repo = new Repositories\DatabaseRepository; + + try { + $password = str_random(20); + $repo->password($database->id, $password); + return response($password); - } catch (\Pterodactyl\Exceptions\DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 503); - } catch(\Exception $ex) { + } catch (DisplayException $ex) { + return response()->json(['error' => $ex->getMessage()], 503); + } catch (\Exception $ex) { Log::error($ex); + return response()->json([ - 'error' => 'An unhandled error occured while attempting to modify this database\'s password.' + 'error' => 'An unhandled error occured while attempting to modify this database\'s password.', ], 503); } } - } diff --git a/app/Http/Controllers/Server/ServerController.php b/app/Http/Controllers/Server/ServerController.php index 810223303..ce6f59595 100644 --- a/app/Http/Controllers/Server/ServerController.php +++ b/app/Http/Controllers/Server/ServerController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,121 +21,141 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Server; -use Auth; -use DB; -use Uuid; -use Alert; use Log; - +use Alert; +use Cache; use Pterodactyl\Models; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; -use Pterodactyl\Repositories\Daemon\FileRepository; -use Pterodactyl\Repositories\ServerRepository; - -use Pterodactyl\Http\Controllers\Controller; use Illuminate\Http\Request; - -use InvalidArgumentException; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\ServerRepository; +use Pterodactyl\Repositories\Daemon\FileRepository; +use Pterodactyl\Exceptions\DisplayValidationException; class ServerController extends Controller { - - /** - * Controller Constructor - * - * @return void - */ - public function __construct() - { - // - } - - public function getJavascript(Request $request, $uuid, $folder, $file) - { - $server = Models\Server::getByUUID($uuid); - - $info = pathinfo($file); - $routeFile = str_replace('/', '.', $info['dirname']) . '.' . $info['filename']; - try { - return response()->view('server.js.' . $folder . '.' . $routeFile, [ - 'server' => $server, - 'node' => Models\Node::find($server->node) - ])->header('Content-Type', 'application/javascript'); - } catch (InvalidArgumentException $ex) { - return abort(404); - } catch (\Exception $ex) { - throw $ex; - } - } - /** * Renders server index page for specified server. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View */ - public function getIndex(Request $request) + public function getIndex(Request $request, $uuid) { - $server = Models\Server::getByUUID($request->route()->server); + $server = Models\Server::byUuid($uuid); + + $server->js([ + 'meta' => [ + 'saveFile' => route('server.files.save', $server->uuidShort), + 'csrfToken' => csrf_token(), + ], + 'config' => [ + 'console_count' => config('pterodactyl.console.count'), + 'console_freq' => config('pterodactyl.console.frequency'), + ], + ]); + return view('server.index', [ 'server' => $server, - 'allocations' => Models\Allocation::where('assigned_to', $server->id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(), - 'node' => Models\Node::find($server->node) + 'node' => $server->node, + ]); + } + + /** + * Renders server console as an individual item. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View + */ + public function getConsole(Request $request, $uuid) + { + \Debugbar::disable(); + $server = Models\Server::byUuid($uuid); + + $server->js([ + 'config' => [ + 'console_count' => config('pterodactyl.console.count'), + 'console_freq' => config('pterodactyl.console.frequency'), + ], + ]); + + return view('server.console', [ + 'server' => $server, + 'node' => $server->node, ]); } /** * Renders file overview page. * - * @param Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View */ - public function getFiles(Request $request) + public function getFiles(Request $request, $uuid) { - - $server = Models\Server::getByUUID($request->route()->server); + $server = Models\Server::byUuid($uuid); $this->authorize('list-files', $server); + $server->js([ + 'meta' => [ + 'directoryList' => route('server.files.directory-list', $server->uuidShort), + 'csrftoken' => csrf_token(), + ], + 'permissions' => [ + 'moveFiles' => $request->user()->can('move-files', $server), + 'copyFiles' => $request->user()->can('copy-files', $server), + 'compressFiles' => $request->user()->can('compress-files', $server), + 'decompressFiles' => $request->user()->can('decompress-files', $server), + 'createFiles' => $request->user()->can('create-files', $server), + 'downloadFiles' => $request->user()->can('download-files', $server), + 'deleteFiles' => $request->user()->can('delete-files', $server), + ], + ]); + return view('server.files.index', [ 'server' => $server, - 'node' => Models\Node::find($server->node) + 'node' => $server->node, ]); } /** * Renders add file page. * - * @param Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View */ - public function getAddFile(Request $request) + public function getAddFile(Request $request, $uuid) { + $server = Models\Server::byUuid($uuid); + $this->authorize('create-files', $server); - $server = Models\Server::getByUUID($request->route()->server); - $this->authorize('add-files', $server); + $server->js(); return view('server.files.add', [ 'server' => $server, - 'node' => Models\Node::find($server->node), - 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/' + 'node' => $server->node, + 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', ]); } /** * Renders edit file page for a given file. * - * @param Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param string $file + * @return \Illuminate\View\View */ public function getEditFile(Request $request, $uuid, $file) { - - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('edit-files', $server); $fileInfo = (object) pathinfo($file); @@ -145,106 +165,169 @@ class ServerController extends Controller $fileContent = $controller->returnFileContents($file); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); + return redirect()->route('server.files.index', $uuid); } catch (\Exception $ex) { Log::error($ex); Alert::danger('An error occured while attempting to load the requested file for editing, please try again.')->flash(); + return redirect()->route('server.files.index', $uuid); } + $server->js([ + 'stat' => $fileContent['stat'], + ]); + return view('server.files.edit', [ 'server' => $server, - 'node' => Models\Node::find($server->node), + 'node' => $server->node, 'file' => $file, 'stat' => $fileContent['stat'], 'contents' => $fileContent['file']->content, - 'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/' + 'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/', ]); - } /** * Handles downloading a file for the user. * - * @param Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param string $file + * @return \Illuminate\View\View */ public function getDownloadFile(Request $request, $uuid, $file) { - - $server = Models\Server::getByUUID($uuid); - $node = Models\Node::find($server->node); - + $server = Models\Server::byUuid($uuid); $this->authorize('download-files', $server); - $download = new Models\Download; - - $download->token = (string) Uuid::generate(4); - $download->server = $server->uuid; - $download->path = $file; - - $download->save(); - - return redirect( $node->scheme . '://' . $node->fqdn . ':' . $node->daemonListen . '/server/file/download/' . $download->token); + $token = str_random(40); + Cache::tags(['Server:Downloads'])->put($token, [ + 'server' => $server->uuid, + 'path' => $file, + ], 5); + return redirect($server->node->scheme . '://' . $server->node->fqdn . ':' . $server->node->daemonListen . '/server/file/download/' . $token); } /** - * Renders server settings page. + * Returns the allocation overview for a server. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View */ - public function getSettings(Request $request, $uuid) + public function getAllocation(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); - $allocation = Models\Allocation::findOrFail($server->allocation); + $server = Models\Server::byUuid($uuid); + $this->authorize('view-allocation', $server); + $server->js(); - $variables = Models\ServiceVariables::select( - 'service_variables.*', - DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_serverValue') - )->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id') - ->where('service_variables.option_id', $server->option) - ->where('server_variables.server_id', $server->id) - ->get(); + return view('server.settings.allocation', [ + 'server' => $server->load(['allocations' => function ($query) { + $query->orderBy('ip', 'asc'); + $query->orderBy('port', 'asc'); + }]), + 'node' => $server->node, + ]); + } - $service = Models\Service::select( - DB::raw('IFNULL(service_options.executable, services.executable) as executable') - )->leftJoin('service_options', 'service_options.parent_service', '=', 'services.id') - ->where('service_options.id', $server->option) - ->where('services.id', $server->service) - ->first(); + /** + * Returns the startup overview for a server. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View + */ + public function getStartup(Request $request, $uuid) + { + $server = Models\Server::byUuid($uuid); + $this->authorize('view-startup', $server); - $serverVariables = [ + $server->load(['node', 'allocation', 'variables']); + $variables = Models\ServiceVariable::where('option_id', $server->option_id)->get(); + + $replacements = [ '{{SERVER_MEMORY}}' => $server->memory, - '{{SERVER_IP}}' => $allocation->ip, - '{{SERVER_PORT}}' => $allocation->port, + '{{SERVER_IP}}' => $server->allocation->ip, + '{{SERVER_PORT}}' => $server->allocation->port, ]; - $processed = str_replace(array_keys($serverVariables), array_values($serverVariables), $server->startup); - foreach($variables as &$variable) { - $replace = ($variable->user_viewable === 1) ? $variable->a_serverValue : '**'; - $processed = str_replace('{{' . $variable->env_variable . '}}', $replace, $processed); + $processed = str_replace(array_keys($replacements), array_values($replacements), $server->startup); + + foreach ($variables as $var) { + if ($var->user_viewable) { + $serverVar = $server->variables->where('variable_id', $var->id)->first(); + $var->server_set_value = $serverVar->variable_value ?? $var->default_value; + } else { + $var->server_set_value = '[hidden]'; + } + + $processed = str_replace('{{' . $var->env_variable . '}}', $var->server_set_value, $processed); } - return view('server.settings', [ + $server->js(); + + return view('server.settings.startup', [ 'server' => $server, - 'databases' => Models\Database::select('databases.*', 'database_servers.host as a_host', 'database_servers.port as a_port') - ->where('server_id', $server->id) - ->join('database_servers', 'database_servers.id', '=', 'databases.db_server') - ->get(), - 'node' => Models\Node::find($server->node), + 'node' => $server->node, 'variables' => $variables->where('user_viewable', 1), - 'service' => $service, + 'service' => $server->service, 'processedStartup' => $processed, ]); } + /** + * Returns the database overview for a server. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View + */ + public function getDatabases(Request $request, $uuid) + { + $server = Models\Server::byUuid($uuid); + $this->authorize('view-databases', $server); + + $server->load('node', 'databases.host'); + $server->js(); + + return view('server.settings.databases', [ + 'server' => $server, + 'node' => $server->node, + 'databases' => $server->databases, + ]); + } + + /** + * Returns the SFTP overview for a server. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View + */ + public function getSFTP(Request $request, $uuid) + { + $server = Models\Server::byUuid($uuid); + $this->authorize('view-sftp', $server); + $server->js(); + + return view('server.settings.sftp', [ + 'server' => $server, + 'node' => $server->node, + ]); + } + + /** + * Handles changing the SFTP password for a server. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\RedirectResponse + */ public function postSettingsSFTP(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('reset-sftp', $server); try { @@ -252,37 +335,42 @@ class ServerController extends Controller $repo->updateSFTPPassword($server->id, $request->input('sftp_pass')); Alert::success('Successfully updated this servers SFTP password.')->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('server.settings', $uuid)->withErrors(json_decode($ex->getMessage())); + return redirect()->route('server.settings.sftp', $uuid)->withErrors(json_decode($ex->getMessage())); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); Alert::danger('An unknown error occured while attempting to update this server\'s SFTP settings.')->flash(); } - return redirect()->route('server.settings', $uuid); + + return redirect()->route('server.settings.sftp', $uuid); } + /** + * Handles changing the startup settings for a server. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\RedirectResponse + */ public function postSettingsStartup(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('edit-startup', $server); try { $repo = new ServerRepository; - $repo->updateStartup($server->id, $request->except([ - '_token' - ])); + $repo->updateStartup($server->id, $request->except('_token')); Alert::success('Server startup variables were successfully updated.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('server.settings.startup', $uuid)->withErrors(json_decode($ex->getMessage())); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); - } catch(\Exception $ex) { + } catch (\Exception $ex) { Log::error($ex); Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. Please try again.')->flash(); } - return redirect()->route('server.settings', [ - 'uuid' => $uuid, - 'tab' => 'tab_startup' - ]); - } + return redirect()->route('server.settings.startup', $uuid); + } } diff --git a/app/Http/Controllers/Server/SubuserController.php b/app/Http/Controllers/Server/SubuserController.php index 963ea99a1..9a8b35075 100644 --- a/app/Http/Controllers/Server/SubuserController.php +++ b/app/Http/Controllers/Server/SubuserController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,95 +21,88 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Server; -use DB; +use Log; use Auth; use Alert; -use Log; - use Pterodactyl\Models; -use Pterodactyl\Repositories\SubuserRepository; - -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - use Illuminate\Http\Request; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\SubuserRepository; +use Pterodactyl\Exceptions\DisplayValidationException; class SubuserController extends Controller { - /** - * Controller Constructor + * Displays the subuser overview index. * - * @return void + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View */ - public function __construct() + public function index(Request $request, $uuid) { - // - } - - public function getIndex(Request $request, $uuid) - { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('subusers.user'); $this->authorize('list-subusers', $server); + $server->js(); + return view('server.users.index', [ 'server' => $server, - 'node' => Models\Node::find($server->node), - 'subusers' => Models\Subuser::select('subusers.*', 'users.email as a_userEmail') - ->join('users', 'users.id', '=', 'subusers.user_id') - ->where('server_id', $server->id) - ->get() + 'node' => $server->node, + 'subusers' => $server->subusers, ]); - } - public function getView(Request $request, $uuid, $id) + /** + * Displays the a single subuser overview. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param int $id + * @return \Illuminate\View\View + */ + public function view(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('node'); $this->authorize('view-subuser', $server); - $subuser = Models\Subuser::select('subusers.*', 'users.email as a_userEmail') - ->join('users', 'users.id', '=', 'subusers.user_id') - ->where(DB::raw('md5(subusers.id)'), $id)->where('subusers.server_id', $server->id) - ->first(); + $subuser = Models\Subuser::with('permissions', 'user') + ->where('server_id', $server->id)->findOrFail($id); - if (!$subuser) { - abort(404); - } - - $permissions = []; - $modelPermissions = Models\Permission::select('permission') - ->where('user_id', $subuser->user_id)->where('server_id', $server->id) - ->get(); - - foreach($modelPermissions as &$perm) { - $permissions[$perm->permission] = true; - } + $server->js(); return view('server.users.view', [ 'server' => $server, - 'node' => Models\Node::find($server->node), + 'node' => $server->node, 'subuser' => $subuser, - 'permissions' => $permissions, + 'permlist' => Models\Permission::listPermissions(), + 'permissions' => $subuser->permissions->mapWithKeys(function ($item, $key) { + return [$item->permission => true]; + }), ]); } - public function postView(Request $request, $uuid, $id) + /** + * Handles editing a subuser. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function update(Request $request, $uuid, $id) { - - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('edit-subuser', $server); - $subuser = Models\Subuser::where(DB::raw('md5(id)'), $id)->where('server_id', $server->id)->first(); + $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id); try { - - if (!$subuser) { - throw new DisplayException('Unable to locate a subuser by that ID.'); - } else if ($subuser->user_id === Auth::user()->id) { + if ($subuser->user_id === Auth::user()->id) { throw new DisplayException('You are not authorized to edit you own account.'); } @@ -117,14 +110,14 @@ class SubuserController extends Controller $repo->update($subuser->id, [ 'permissions' => $request->input('permissions'), 'server' => $server->id, - 'user' => $subuser->user_id + 'user' => $subuser->user_id, ]); Alert::success('Subuser permissions have successfully been updated.')->flash(); } catch (DisplayValidationException $ex) { return redirect()->route('server.subusers.view', [ 'uuid' => $uuid, - 'id' => $id + 'id' => $id, ])->withErrors(json_decode($ex->getMessage())); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); @@ -132,37 +125,55 @@ class SubuserController extends Controller Log::error($ex); Alert::danger('An unknown error occured while attempting to update this subuser.')->flash(); } + return redirect()->route('server.subusers.view', [ 'uuid' => $uuid, - 'id' => $id + 'id' => $id, ]); } - public function getNew(Request $request, $uuid) + /** + * Display new subuser creation page. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View + */ + public function create(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('create-subuser', $server); + $server->js(); return view('server.users.new', [ 'server' => $server, - 'node' => Models\Node::find($server->node) + 'permissions' => Models\Permission::listPermissions(), + 'node' => $server->node, ]); } - public function postNew(Request $request, $uuid) + /** + * Handles creating a new subuser. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\RedirectResponse + */ + public function store(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('create-subuser', $server); try { $repo = new SubuserRepository; - $id = $repo->create($server->id, $request->except([ - '_token' + $subuser = $repo->create($server->id, $request->only([ + 'permissions', 'email', ])); Alert::success('Successfully created new subuser.')->flash(); + return redirect()->route('server.subusers.view', [ 'uuid' => $uuid, - 'id' => md5($id) + 'id' => $subuser->id, ]); } catch (DisplayValidationException $ex) { return redirect()->route('server.subusers.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); @@ -172,33 +183,39 @@ class SubuserController extends Controller Log::error($ex); Alert::danger('An unknown error occured while attempting to add a new subuser.')->flash(); } + return redirect()->route('server.subusers.new', $uuid)->withInput(); } - public function deleteSubuser(Request $request, $uuid, $id) + /** + * Handles deleting a subuser. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param int $id + * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response + */ + public function delete(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('delete-subuser', $server); try { - $subuser = Models\Subuser::select('id')->where(DB::raw('md5(id)'), $id)->where('server_id', $server->id)->first(); - if (!$subuser) { - throw new DisplayException('No subuser by that ID was found on the system.'); - } + $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id); $repo = new SubuserRepository; $repo->delete($subuser->id); + return response('', 204); } catch (DisplayException $ex) { response()->json([ - 'error' => $ex->getMessage() + 'error' => $ex->getMessage(), ], 422); } catch (\Exception $ex) { Log::error($ex); response()->json([ - 'error' => 'An unknown error occured while attempting to delete this subuser.' + 'error' => 'An unknown error occured while attempting to delete this subuser.', ], 503); } } - } diff --git a/app/Http/Controllers/Server/TaskController.php b/app/Http/Controllers/Server/TaskController.php index 0ef5e7bc5..a3908943a 100644 --- a/app/Http/Controllers/Server/TaskController.php +++ b/app/Http/Controllers/Server/TaskController.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,136 +21,160 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Server; -use Alert; use Log; -use Cron; - -use Pterodactyl\Repositories; -use Pterodactyl\Models; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -use Pterodactyl\Http\Controllers\Controller; +use Alert; use Illuminate\Http\Request; +use Pterodactyl\Models\Server; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\TaskRepository; +use Pterodactyl\Exceptions\DisplayValidationException; class TaskController extends Controller { - public function __constructor() + /** + * Display task index page. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View + */ + public function index(Request $request, $uuid) { - // - } - - public function getIndex(Request $request, $uuid) - { - $server = Models\Server::getByUUID($uuid); + $server = Server::byUuid($uuid)->load('tasks'); $this->authorize('list-tasks', $server); + $server->js(); return view('server.tasks.index', [ 'server' => $server, - 'node' => Models\Node::findOrFail($server->node), - 'tasks' => Models\Task::where('server', $server->id)->get(), + 'node' => $server->node, + 'tasks' => $server->tasks, 'actions' => [ - 'command' => 'Send Command', - 'power' => 'Set Power Status' - ] + 'command' => trans('server.tasks.actions.command'), + 'power' => trans('server.tasks.actions.power'), + ], ]); } - public function getNew(Request $request, $uuid) + /** + * Display new task page. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\View\View + */ + public function create(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Server::byUuid($uuid); $this->authorize('create-task', $server); + $server->js(); return view('server.tasks.new', [ 'server' => $server, - 'node' => Models\Node::findOrFail($server->node) + 'node' => $server->node, ]); } - public function postNew(Request $request, $uuid) + /** + * Handle creation of new task. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\RedirectResponse + */ + public function store(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Server::byUuid($uuid); $this->authorize('create-task', $server); + $repo = new TaskRepository; try { - $repo = new Repositories\TaskRepository; - $repo->create($server->id, $request->except([ - '_token' + $repo->create($server->id, $request->user()->id, $request->except([ + '_token', ])); + + return redirect()->route('server.tasks', $uuid); } catch (DisplayValidationException $ex) { - return redirect()->route('server.tasks', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); + return redirect()->route('server.tasks.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); Alert::danger('An unknown error occured while attempting to create this task.')->flash(); } - return redirect()->route('server.tasks', $uuid); + return redirect()->route('server.tasks.new', $uuid); } - public function getView(Request $request, $uuid, $id) + /** + * Handle deletion of a task. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param int $id + * @return \Illuminate\Http\JsonResponse + */ + public function delete(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); - $this->authorize('view-task', $server); - - return view('server.tasks.view', [ - 'server' => $server, - 'node' => Models\Node::findOrFail($server->node), - 'task' => Models\Task::where('id', $id)->where('server', $server->id)->firstOrFail() - ]); - } - - public function deleteTask(Request $request, $uuid, $id) - { - $server = Models\Server::getByUUID($uuid); + $server = Server::byUuid($uuid)->load('tasks'); $this->authorize('delete-task', $server); - $task = Models\Task::findOrFail($id); - - if (!$task || $server->id !== $task->server) { + $task = $server->tasks->where('id', $id)->first(); + if (! $task) { return response()->json([ - 'error' => 'No task by that ID was found associated with this server.' + 'error' => 'No task by that ID was found associated with this server.', ], 404); } + $repo = new TaskRepository; try { - $repo = new Repositories\TaskRepository; $repo->delete($id); + return response()->json([], 204); } catch (\Exception $ex) { Log::error($ex); + return response()->json([ - 'error' => 'A server error occured while attempting to delete this task.' + 'error' => 'A server error occured while attempting to delete this task.', ], 503); } } - public function toggleTask(Request $request, $uuid, $id) + /** + * Toggle the status of a task. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param int $id + * @return \Illuminate\Http\JsonResponse + */ + public function toggle(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); + $server = Server::byUuid($uuid)->load('tasks'); $this->authorize('toggle-task', $server); - $task = Models\Task::findOrFail($id); - - if (!$task || $server->id !== $task->server) { + $task = $server->tasks->where('id', $id)->first(); + if (! $task) { return response()->json([ - 'error' => 'No task by that ID was found associated with this server.' + 'error' => 'No task by that ID was found associated with this server.', ], 404); } + $repo = new TaskRepository; try { - $repo = new Repositories\TaskRepository; $resp = $repo->toggle($id); + return response()->json([ - 'status' => $resp + 'status' => $resp, ]); } catch (\Exception $ex) { Log::error($ex); + return response()->json([ - 'error' => 'A server error occured while attempting to toggle this task.' + 'error' => 'A server error occured while attempting to toggle this task.', ], 503); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 9e8d9f816..a70895a3e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -15,9 +15,12 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, \Pterodactyl\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - \Pterodactyl\Http\Middleware\LanguageMiddleware::class, + \Pterodactyl\Http\Middleware\TrimStrings::class, + + /* + * Custom middleware applied to all routes. + */ + \Fideloper\Proxy\TrustProxies::class, ]; /** @@ -33,8 +36,10 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, + \Pterodactyl\Http\Middleware\LanguageMiddleware::class, ], 'api' => [ + \Pterodactyl\Http\Middleware\HMACAuthorization::class, 'throttle:60,1', 'bindings', ], @@ -51,9 +56,11 @@ class Kernel extends HttpKernel 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, 'server' => \Pterodactyl\Http\Middleware\CheckServer::class, 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, + 'daemon' => \Pterodactyl\Http\Middleware\DaemonAuthenticate::class, 'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, + 'recaptcha' => \Pterodactyl\Http\Middleware\VerifyReCaptcha::class, ]; } diff --git a/app/Http/Middleware/APISecretToken.php b/app/Http/Middleware/APISecretToken.php deleted file mode 100755 index fab4d177d..000000000 --- a/app/Http/Middleware/APISecretToken.php +++ /dev/null @@ -1,136 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Middleware; - -use Auth; -use Crypt; -use Config; -use IPTools\IP; -use IPTools\Range; - -use Pterodactyl\Models\APIKey; -use Pterodactyl\Models\APIPermission; -use Pterodactyl\Models\User; -use Pterodactyl\Services\APILogService; - -use Illuminate\Http\Request; -use Dingo\Api\Routing\Route; -use Dingo\Api\Auth\Provider\Authorization; - -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; // 400 -use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; // 401 -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; // 403 -use Symfony\Component\HttpKernel\Exception\HttpException; //500 - -class APISecretToken extends Authorization -{ - - protected $algo = 'sha256'; - - protected $permissionAllowed = false; - - protected $url = ''; - - public function __construct() - { - Config::set('session.driver', 'array'); - } - - public function getAuthorizationMethod() - { - return 'Authorization'; - } - - public function authenticate(Request $request, Route $route) - { - if (!$request->bearerToken() || empty($request->bearerToken())) { - APILogService::log($request, 'The authentication header was missing or malformed.'); - throw new UnauthorizedHttpException('The authentication header was missing or malformed.'); - } - - list($public, $hashed) = explode('.', $request->bearerToken()); - - $key = APIKey::where('public', $public)->first(); - if (!$key) { - APILogService::log($request, 'Invalid API Key.'); - throw new AccessDeniedHttpException('Invalid API Key.'); - } - - // Check for Resource Permissions - if (!empty($request->route()->getName())) { - if(!is_null($key->allowed_ips)) { - $inRange = false; - foreach(json_decode($key->allowed_ips) as $ip) { - if (Range::parse($ip)->contains(new IP($request->ip()))) { - $inRange = true; - break; - } - } - if (!$inRange) { - APILogService::log($request, 'This IP address <' . $request->ip() . '> does not have permission to use this API key.'); - throw new AccessDeniedHttpException('This IP address <' . $request->ip() . '> does not have permission to use this API key.'); - } - } - - $permission = APIPermission::where('key_id', $key->id)->where('permission', $request->route()->getName()); - - // Suport Wildcards - if (starts_with($request->route()->getName(), 'api.user')) { - $permission->orWhere('permission', 'api.user.*'); - } else if(starts_with($request->route()->getName(), 'api.admin')) { - $permission->orWhere('permission', 'api.admin.*'); - } - - if (!$permission->first()) { - APILogService::log($request, 'You do not have permission to access this resource. This API Key requires the ' . $request->route()->getName() . ' permission node.'); - throw new AccessDeniedHttpException('You do not have permission to access this resource. This API Key requires the ' . $request->route()->getName() . ' permission node.'); - } - } - - try { - $decrypted = Crypt::decrypt($key->secret); - } catch (\Illuminate\Contracts\Encryption\DecryptException $ex) { - APILogService::log($request, 'There was an error while attempting to check your secret key.'); - throw new HttpException('There was an error while attempting to check your secret key.'); - } - - $this->url = urldecode($request->fullUrl()); - if($this->_generateHMAC($request->getContent(), $decrypted) !== base64_decode($hashed)) { - APILogService::log($request, 'The hashed body was not valid. Potential modification of contents in route.'); - throw new BadRequestHttpException('The hashed body was not valid. Potential modification of contents in route.'); - } - - // Log the Route Access - APILogService::log($request, null, true); - return Auth::loginUsingId($key->user); - - } - - protected function _generateHMAC($body, $key) - { - $data = $this->url . $body; - return hash_hmac($this->algo, $data, $key, true); - } - -} diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index e4c3f91bb..175210929 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Middleware; use Closure; @@ -31,14 +32,14 @@ class AdminAuthenticate /** * The Guard implementation. * - * @var Guard + * @var \Illuminate\Contracts\Auth\Guard */ protected $auth; /** * Create a new filter instance. * - * @param Guard $auth + * @param \Illuminate\Contracts\Auth\Guard $auth * @return void */ public function __construct(Guard $auth) @@ -50,7 +51,7 @@ class AdminAuthenticate * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) @@ -63,7 +64,7 @@ class AdminAuthenticate } } - if($this->auth->user()->root_admin !== 1) { + if ($this->auth->user()->root_admin !== 1) { return abort(403); } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 216465e23..b6db005ef 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -10,14 +10,14 @@ class Authenticate /** * The Guard implementation. * - * @var Guard + * @var \Illuminate\Contracts\Auth\Guard */ protected $auth; /** * Create a new filter instance. * - * @param Guard $auth + * @param \Illuminate\Contracts\Auth\Guard $auth * @return void */ public function __construct(Guard $auth) @@ -29,7 +29,7 @@ class Authenticate * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) diff --git a/app/Http/Middleware/CheckServer.php b/app/Http/Middleware/CheckServer.php index 7ae0c115f..4cfe08191 100644 --- a/app/Http/Middleware/CheckServer.php +++ b/app/Http/Middleware/CheckServer.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,43 +21,109 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Middleware; -use Closure; use Auth; +use Closure; +use Illuminate\Http\Request; use Pterodactyl\Models\Server; -use Debugbar; +use Illuminate\Auth\AuthenticationException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class CheckServer { + /** + * The elquent model for the server. + * + * @var \Pterodactyl\Models\Server + */ + protected $server; + + /** + * The request object. + * + * @var \Illuminate\Http\Request + */ + protected $request; + /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { - - if (!Auth::user()) { - return redirect()->guest('auth/login'); + if (! Auth::user()) { + throw new AuthenticationException(); } - $server = Server::getByUUID($request->route()->server); - if (!$server) { + $this->request = $request; + $this->server = Server::byUuid($request->route()->server); + + if (! $this->exists()) { return response()->view('errors.404', [], 404); } - if ($server->suspended === 1) { + if ($this->suspended()) { return response()->view('errors.suspended', [], 403); } - if ($server->installed !== 1) { + if (! $this->installed()) { return response()->view('errors.installing', [], 403); } return $next($request); + } + /** + * Determine if the server was found on the system. + * + * @return bool + */ + protected function exists() + { + if (! $this->server) { + if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { + throw new NotFoundHttpException('The requested server was not found on the system.'); + } + } + + return (! $this->server) ? false : true; + } + + /** + * Determine if the server is suspended. + * + * @return bool + */ + protected function suspended() + { + if ($this->server->suspended) { + if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { + throw new AccessDeniedHttpException('Server is suspended.'); + } + } + + return $this->server->suspended; + } + + /** + * Determine if the server is installed. + * + * @return bool + */ + protected function installed() + { + if ($this->server->installed !== 1) { + if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { + throw new AccessDeniedHttpException('Server is completing install process.'); + } + } + + return $this->server->installed === 1; } } diff --git a/app/Http/Middleware/DaemonAuthenticate.php b/app/Http/Middleware/DaemonAuthenticate.php new file mode 100644 index 000000000..b924b6fb5 --- /dev/null +++ b/app/Http/Middleware/DaemonAuthenticate.php @@ -0,0 +1,84 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Middleware; + +use Closure; +use Pterodactyl\Models\Node; +use Illuminate\Contracts\Auth\Guard; + +class DaemonAuthenticate +{ + /** + * The Guard implementation. + * + * @var \Illuminate\Contracts\Auth\Guard + */ + protected $auth; + + /** + * An array of route names to not apply this middleware to. + * + * @var array + */ + protected $except = [ + 'daemon.configuration', + ]; + + /** + * Create a new filter instance. + * + * @param \Illuminate\Contracts\Auth\Guard $auth + * @return void + */ + public function __construct(Guard $auth) + { + $this->auth = $auth; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + if (in_array($request->route()->getName(), $this->except)) { + return $next($request); + } + + if (! $request->header('X-Access-Node')) { + return abort(403); + } + + $node = Node::where('daemonSecret', $request->header('X-Access-Node'))->first(); + if (! $node) { + return abort(401); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/HMACAuthorization.php b/app/Http/Middleware/HMACAuthorization.php new file mode 100644 index 000000000..4f5c86f2b --- /dev/null +++ b/app/Http/Middleware/HMACAuthorization.php @@ -0,0 +1,229 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Middleware; + +use Auth; +use Crypt; +use Config; +use Closure; +use Debugbar; +use IPTools\IP; +use IPTools\Range; +use Illuminate\Http\Request; +use Pterodactyl\Models\APIKey; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; // 400 +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; // 403 + +class HMACAuthorization +{ + /** + * The algorithm to use for handling HMAC encryption. + * + * @var string + */ + const HMAC_ALGORITHM = 'sha256'; + + /** + * Stored values from the Authorization header. + * + * @var array + */ + protected $token = []; + + /** + * The eloquent model for the API key. + * + * @var \Pterodactyl\Models\APIKey + */ + protected $key; + + /** + * The illuminate request object. + * + * @var \Illuminate\Http\Request + */ + private $request; + + /** + * Construct class instance. + * + * @return void + */ + public function __construct() + { + Debugbar::disable(); + Config::set('session.driver', 'array'); + } + + /** + * Handle an incoming request for the API. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + $this->request = $request; + + $this->checkBearer(); + $this->validateRequest(); + $this->validateIPAccess(); + $this->validateContents(); + + Auth::loginUsingId($this->key()->user_id); + + return $next($request); + } + + /** + * Checks that the Bearer token is provided and in a valid format. + * + * @return void + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + protected function checkBearer() + { + if (empty($this->request()->bearerToken())) { + throw new BadRequestHttpException('Request was missing required Authorization header.'); + } + + $token = explode('.', $this->request()->bearerToken()); + if (count($token) !== 2) { + throw new BadRequestHttpException('The Authorization header passed was not in a validate public/private key format.'); + } + + $this->token = [ + 'public' => $token[0], + 'hash' => $token[1], + ]; + } + + /** + * Determine if the request contains a valid public API key + * as well as permissions for the resource. + * + * @return void + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + protected function validateRequest() + { + $this->key = APIKey::where('public', $this->public())->first(); + if (! $this->key) { + throw new AccessDeniedHttpException('Unable to identify requester. Authorization token is invalid.'); + } + } + + /** + * Determine if the requesting IP address is allowed to use this API key. + * + * @return bool + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + protected function validateIPAccess() + { + if (! is_null($this->key()->allowed_ips)) { + foreach (json_decode($this->key()->allowed_ips) as $ip) { + if (Range::parse($ip)->contains(new IP($this->request()->ip()))) { + return true; + } + } + + throw new AccessDeniedHttpException('This IP address does not have permission to access the API using these credentials.'); + } + + return true; + } + + /** + * Determine if the HMAC sent in the request matches the one generated + * on the panel side. + * + * @return void + * + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + protected function validateContents() + { + if (! hash_equals(base64_decode($this->hash()), $this->generateSignature())) { + throw new BadRequestHttpException('The HMAC for the request was invalid.'); + } + } + + /** + * Generate a HMAC from the request and known API secret key. + * + * @return string + */ + protected function generateSignature() + { + $content = urldecode($this->request()->fullUrl()) . $this->request()->getContent(); + + return hash_hmac(self::HMAC_ALGORITHM, $content, Crypt::decrypt($this->key()->secret), true); + } + + /** + * Return the public key passed in the Authorization header. + * + * @return string + */ + protected function public() + { + return $this->token['public']; + } + + /** + * Return the base64'd HMAC sent in the Authorization header. + * + * @return string + */ + protected function hash() + { + return $this->token['hash']; + } + + /** + * Return the API Key model. + * + * @return \Pterodactyl\Models\APIKey + */ + protected function key() + { + return $this->key; + } + + /** + * Return the Illuminate Request object. + * + * @return \Illuminate\Http\Request + */ + private function request() + { + return $this->request; + } +} diff --git a/app/Http/Middleware/LanguageMiddleware.php b/app/Http/Middleware/LanguageMiddleware.php index 5b52705c6..44553ebef 100644 --- a/app/Http/Middleware/LanguageMiddleware.php +++ b/app/Http/Middleware/LanguageMiddleware.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,40 +21,36 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Middleware; use Auth; use Closure; use Session; use Settings; - use Illuminate\Support\Facades\App; class LanguageMiddleware { - - public function __construct() - { - // - } - /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { - if (Session::has('applocale')) { - App::setLocale(Session::get('applocale')); - } else if(Auth::check() && isset(Auth::user()->language)) { - Session::set('applocale', Auth::user()->language); - App::setLocale(Auth::user()->language); - } else { - App::setLocale(Settings::get('default_language', 'en')); - } + // if (Session::has('applocale')) { + // App::setLocale(Session::get('applocale')); + // } elseif (Auth::check() && isset(Auth::user()->language)) { + // Session::put('applocale', Auth::user()->language); + // App::setLocale(Auth::user()->language); + // } else { + // App::setLocale(Settings::get('default_language', 'en')); + // } + App::setLocale('en'); + return $next($request); } } diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 1735e49fe..731a1767f 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -11,14 +11,14 @@ class RedirectIfAuthenticated * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @param string|null $guard + * @param \Closure $next + * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { - return redirect('/'); + return redirect(route('index')); } return $next($request); diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php new file mode 100644 index 000000000..04f434b98 --- /dev/null +++ b/app/Http/Middleware/TrimStrings.php @@ -0,0 +1,18 @@ +has('g-recaptcha-response')) { + $client = new \GuzzleHttp\Client(); + $res = $client->post(config('recaptcha.domain'), [ + 'form_params' => [ + 'secret' => config('recaptcha.secret_key'), + 'response' => $request->input('g-recaptcha-response'), + ], + ]); + + if ($res->getStatusCode() === 200) { + $result = json_decode($res->getBody()); + + $verified = function ($result, $request) { + if (! config('recaptcha.verify_domain')) { + return false; + } + + $url = parse_url($request->url()); + + if (! array_key_exists('host', $url)) { + return false; + } + + return $result->hostname === $url['host']; + }; + + if ($result->success && (! config('recaptcha.verify_domain') || $verified($result, $request))) { + return $next($request); + } + } + } + + // Emit an event and return to the previous view with an error (only the captcha error will be shown!) + event(new FailedCaptcha($request->ip(), (! isset($result->hostname) ?: $result->hostname))); + + return back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput(); + } +} diff --git a/app/Http/Routes/APIRoutes.php b/app/Http/Routes/APIRoutes.php deleted file mode 100755 index 33d3d7177..000000000 --- a/app/Http/Routes/APIRoutes.php +++ /dev/null @@ -1,182 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Routes; - -use Pterodactyl\Models; -use Illuminate\Routing\Router; - -class APIRoutes -{ - - public function map(Router $router) { - - $api = app('Dingo\Api\Routing\Router'); - $api->version('v1', ['prefix' => 'api/me', 'middleware' => 'api.auth'], function ($api) { - $api->get('/', [ - 'as' => 'api.user.me', - 'uses' => 'Pterodactyl\Http\Controllers\API\User\InfoController@me' - ]); - - $api->get('/server/{uuid}', [ - 'as' => 'api.user.server', - 'uses' => 'Pterodactyl\Http\Controllers\API\User\ServerController@info' - ]); - - $api->put('/server/{uuid}', [ - 'as' => 'api.user.server.power', - 'uses' => 'Pterodactyl\Http\Controllers\API\User\ServerController@power' - ]); - }); - - $api->version('v1', ['prefix' => 'api', 'middleware' => 'api.auth'], function ($api) { - - /** - * User Routes - */ - $api->get('users', [ - 'as' => 'api.admin.users.list', - 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@list' - ]); - - $api->post('users', [ - 'as' => 'api.admin.users.create', - 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@create' - ]); - - $api->get('users/{id}', [ - 'as' => 'api.admin.users.view', - 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@view' - ]); - - $api->patch('users/{id}', [ - 'as' => 'api.admin.users.update', - 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@update' - ]); - - $api->delete('users/{id}', [ - 'as' => 'api.admin.users.delete', - 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@delete' - ]); - - /** - * Server Routes - */ - $api->get('servers', [ - 'as' => 'api.admin.servers.list', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@list' - ]); - - $api->post('servers', [ - 'as' => 'api.admin.servers.create', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@create' - ]); - - $api->get('servers/{id}', [ - 'as' => 'api.admin.servers.view', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@view' - ]); - - $api->patch('servers/{id}/config', [ - 'as' => 'api.admin.servers.config', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@config' - ]); - - $api->patch('servers/{id}/build', [ - 'as' => 'api.admin.servers.build', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@build' - ]); - - $api->post('servers/{id}/suspend', [ - 'as' => 'api.admin.servers.suspend', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@suspend' - ]); - - $api->post('servers/{id}/unsuspend', [ - 'as' => 'api.admin.servers.unsuspend', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@unsuspend' - ]); - - $api->delete('servers/{id}/{force?}', [ - 'as' => 'api.admin.servers.delete', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@delete' - ]); - - /** - * Node Routes - */ - $api->get('nodes', [ - 'as' => 'api.admin.nodes.list', - 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@list' - ]); - - $api->post('nodes', [ - 'as' => 'api.admin.nodes.create', - 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@create' - ]); - - $api->get('nodes/allocations', [ - 'as' => 'api.admin.nodes.allocations', - 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@allocations' - ]); - - $api->get('nodes/{id}', [ - 'as' => 'api.admin.nodes.view', - 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@view' - ]); - - $api->get('nodes/{id}/config', [ - 'as' => 'api.admin.nodes.view', - 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@config' - ]); - - $api->delete('nodes/{id}', [ - 'as' => 'api.admin.nodes.delete', - 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@delete' - ]); - - /** - * Location Routes - */ - $api->get('locations', [ - 'as' => 'api.admin.locations.list', - 'uses' => 'Pterodactyl\Http\Controllers\API\LocationController@list' - ]); - - /** - * Service Routes - */ - $api->get('services', [ - 'as' => 'api.admin.services.list', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@list' - ]); - - $api->get('services/{id}', [ - 'as' => 'api.admin.services.view', - 'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@view' - ]); - - }); - } - -} diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php deleted file mode 100644 index bfc07b725..000000000 --- a/app/Http/Routes/AdminRoutes.php +++ /dev/null @@ -1,426 +0,0 @@ - - * Some Modifications (c) 2015 Dylan Seidt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Routes; - -use Illuminate\Routing\Router; - -class AdminRoutes { - - public function map(Router $router) { - - // Admin Index - $router->get('admin', [ - 'as' => 'admin.index', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ], - 'uses' => 'Admin\BaseController@getIndex' - ]); - - $router->group([ - 'prefix' => 'admin/settings', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ] - ], function () use ($router) { - $router->get('/', [ - 'as' => 'admin.settings', - 'uses' => 'Admin\BaseController@getSettings' - ]); - $router->post('/', [ - 'uses' => 'Admin\BaseController@postSettings' - ]); - }); - - $router->group([ - 'prefix' => 'admin/users', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ] - ], function () use ($router) { - - // View All Accounts on System - $router->get('/', [ - 'as' => 'admin.users', - 'uses' => 'Admin\UserController@getIndex' - ]); - - $router->get('/accounts.json', [ - 'as' => 'admin.users.json', - 'uses' => 'Admin\UserController@getJson' - ]); - - // View Specific Account - $router->get('/view/{id}', [ - 'as' => 'admin.users.view', - 'uses' => 'Admin\UserController@getView' - ]); - - // View Specific Account - $router->post('/view/{id}', [ - 'uses' => 'Admin\UserController@updateUser' - ]); - - // Delete an Account Matching an ID - $router->delete('/view/{id}', [ - 'uses' => 'Admin\UserController@deleteUser' - ]); - - // Show Create Account Page - $router->get('/new', [ - 'as' => 'admin.users.new', - 'uses' => 'Admin\UserController@getNew' - ]); - - // Handle Creating New Account - $router->post('/new', [ - 'uses' => 'Admin\UserController@postNew' - ]); - - }); - - // Server Routes - $router->group([ - 'prefix' => 'admin/servers', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ] - ], function () use ($router) { - - // View All Servers - $router->get('/', [ - 'as' => 'admin.servers', - 'uses' => 'Admin\ServersController@getIndex' ]); - - // View Create Server Page - $router->get('/new', [ - 'as' => 'admin.servers.new', - 'uses' => 'Admin\ServersController@getNew' - ]); - - // Handle POST Request for Creating Server - $router->post('/new', [ - 'uses' => 'Admin\ServersController@postNewServer' - ]); - - // Assorted Page Helpers - $router->post('/new/get-nodes', [ - 'uses' => 'Admin\ServersController@postNewServerGetNodes' - ]); - - $router->post('/new/get-ips', [ - 'uses' => 'Admin\ServersController@postNewServerGetIps' - ]); - - $router->post('/new/service-options', [ - 'uses' => 'Admin\ServersController@postNewServerServiceOptions' - ]); - - $router->post('/new/service-variables', [ - 'uses' => 'Admin\ServersController@postNewServerServiceVariables' - ]); - // End Assorted Page Helpers - - // View Specific Server - $router->get('/view/{id}', [ - 'as' => 'admin.servers.view', - 'uses' => 'Admin\ServersController@getView' - ]); - - // Database Stuffs - $router->post('/view/{id}/database', [ - 'as' => 'admin.servers.database', - 'uses' => 'Admin\ServersController@postDatabase' - ]); - - // Change Server Details - $router->post('/view/{id}/details', [ - 'uses' => 'Admin\ServersController@postUpdateServerDetails' - ]); - - // Change Server Details - $router->post('/view/{id}/container', [ - 'as' => 'admin.servers.post.container', - 'uses' => 'Admin\ServersController@postUpdateContainerDetails' - ]); - - // Change Server Details - $router->post('/view/{id}/startup', [ - 'as' => 'admin.servers.post.startup', - 'uses' => 'Admin\ServersController@postUpdateServerStartup' - ]); - - // Rebuild Server - $router->post('/view/{id}/rebuild', [ - 'uses' => 'Admin\ServersController@postUpdateServerToggleBuild' - ]); - - // Change Build Details - $router->post('/view/{id}/build', [ - 'uses' => 'Admin\ServersController@postUpdateServerUpdateBuild' - ]); - - // Suspend Server - $router->post('/view/{id}/suspend', [ - 'uses' => 'Admin\ServersController@postSuspendServer' - ]); - - // Unsuspend Server - $router->post('/view/{id}/unsuspend', [ - 'uses' => 'Admin\ServersController@postUnsuspendServer' - ]); - - // Change Install Status - $router->post('/view/{id}/installed', [ - 'uses' => 'Admin\ServersController@postToggleInstall' - ]); - - // Delete [force delete] - $router->delete('/view/{id}/{force?}', [ - 'uses' => 'Admin\ServersController@deleteServer' - ]); - - $router->post('/view/{id}/queuedDeletion', [ - 'uses' => 'Admin\ServersController@postQueuedDeletionHandler', - 'as' => 'admin.servers.post.queuedDeletion' - ]); - - }); - - // Node Routes - $router->group([ - 'prefix' => 'admin/nodes', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ] - ], function () use ($router) { - - // View All Nodes - $router->get('/', [ - 'as' => 'admin.nodes', - 'uses' => 'Admin\NodesController@getIndex' - ]); - - // Add New Node - $router->get('/new', [ - 'as' => 'admin.nodes.new', - 'uses' => 'Admin\NodesController@getNew' - ]); - - $router->post('/new', [ - 'uses' => 'Admin\NodesController@postNew' - ]); - - // View Node - $router->get('/view/{id}', [ - 'as' => 'admin.nodes.view', - 'uses' => 'Admin\NodesController@getView' - ]); - - $router->post('/view/{id}', [ - 'uses' => 'Admin\NodesController@postView' - ]); - - $router->delete('/view/{id}/deallocate/single/{allocation}', [ - 'uses' => 'Admin\NodesController@deallocateSingle' - ]); - - $router->post('/view/{id}/deallocate/block', [ - 'uses' => 'Admin\NodesController@deallocateBlock' - ]); - - $router->post('/view/{id}/alias', [ - 'as' => 'admin.nodes.alias', - 'uses' => 'Admin\NodesController@setAlias' - ]); - - $router->get('/view/{id}/allocations.json', [ - 'as' => 'admin.nodes.view.allocations', - 'uses' => 'Admin\NodesController@getAllocationsJson' - ]); - - $router->post('/view/{id}/allocations', [ - 'as' => 'admin.nodes.post.allocations', - 'uses' => 'Admin\NodesController@postAllocations' - ]); - - // View Deploy - $router->get('/view/{id}/deploy', [ - 'as' => 'admin.nodes.deply', - 'uses' => 'Admin\NodesController@getScript' - ]); - - $router->delete('/view/{id}', [ - 'as' => 'admin.nodes.delete', - 'uses' => 'Admin\NodesController@deleteNode' - ]); - - }); - - // Location Routes - $router->group([ - 'prefix' => 'admin/locations', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ] - ], function () use ($router) { - $router->get('/', [ - 'as' => 'admin.locations', - 'uses' => 'Admin\LocationsController@getIndex' - ]); - $router->delete('/{id}', [ - 'uses' => 'Admin\LocationsController@deleteLocation' - ]); - $router->patch('/{id}', [ - 'uses' => 'Admin\LocationsController@patchLocation' - ]); - $router->post('/', [ - 'uses' => 'Admin\LocationsController@postLocation' - ]); - }); - - // Database Routes - $router->group([ - 'prefix' => 'admin/databases', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ] - ], function () use ($router) { - $router->get('/', [ - 'as' => 'admin.databases', - 'uses' => 'Admin\DatabaseController@getIndex' - ]); - $router->get('/new', [ - 'as' => 'admin.databases.new', - 'uses' => 'Admin\DatabaseController@getNew' - ]); - $router->post('/new', [ - 'uses' => 'Admin\DatabaseController@postNew' - ]); - $router->delete('/delete/{id}', [ - 'as' => 'admin.databases.delete', - 'uses' => 'Admin\DatabaseController@deleteDatabase' - ]); - $router->delete('/delete-server/{id}', [ - 'as' => 'admin.databases.delete-server', - 'uses' => 'Admin\DatabaseController@deleteServer' - ]); - }); - - // Service Routes - $router->group([ - 'prefix' => 'admin/services', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ] - ], function () use ($router) { - $router->get('/', [ - 'as' => 'admin.services', - 'uses' => 'Admin\ServiceController@getIndex' - ]); - - $router->get('/new', [ - 'as' => 'admin.services.new', - 'uses' => 'Admin\ServiceController@getNew' - ]); - - $router->post('/new', [ - 'uses' => 'Admin\ServiceController@postNew' - ]); - - $router->get('/service/{id}', [ - 'as' => 'admin.services.service', - 'uses' => 'Admin\ServiceController@getService' - ]); - - $router->post('/service/{id}', [ - 'uses' => 'Admin\ServiceController@postService' - ]); - - $router->delete('/service/{id}', [ - 'uses' => 'Admin\ServiceController@deleteService' - ]); - - $router->get('/service/{service}/option/new', [ - 'as' => 'admin.services.option.new', - 'uses' => 'Admin\ServiceController@newOption' - ]); - - $router->post('/service/{service}/option/new', [ - 'uses' => 'Admin\ServiceController@postNewOption' - ]); - - $router->get('/service/{service}/option/{option}', [ - 'as' => 'admin.services.option', - 'uses' => 'Admin\ServiceController@getOption' - ]); - - $router->post('/service/{service}/option/{option}', [ - 'uses' => 'Admin\ServiceController@postOption' - ]); - - $router->delete('/service/{service}/option/{id}', [ - 'uses' => 'Admin\ServiceController@deleteOption' - ]); - - $router->get('/service/{service}/option/{option}/variable/new', [ - 'as' => 'admin.services.option.variable.new', - 'uses' => 'Admin\ServiceController@getNewVariable' - ]); - - $router->post('/service/{service}/option/{option}/variable/new', [ - 'uses' => 'Admin\ServiceController@postNewVariable' - ]); - - $router->post('/service/{service}/option/{option}/variable/{variable}', [ - 'as' => 'admin.services.option.variable', - 'uses' => 'Admin\ServiceController@postOptionVariable' - ]); - - $router->get('/service/{service}/option/{option}/variable/{variable}/delete', [ - 'as' => 'admin.services.option.variable.delete', - 'uses' => 'Admin\ServiceController@deleteVariable' - ]); - }); - - } - -} diff --git a/app/Http/Routes/AuthRoutes.php b/app/Http/Routes/AuthRoutes.php deleted file mode 100644 index a447f3c5e..000000000 --- a/app/Http/Routes/AuthRoutes.php +++ /dev/null @@ -1,92 +0,0 @@ - - * Some Modifications (c) 2015 Dylan Seidt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Routes; - -use Illuminate\Routing\Router; -use Request; -use Pterodactyl\Models\User as User; - -use Auth; -class AuthRoutes { - - public function map(Router $router) { - $router->group([ - 'prefix' => 'auth', - 'middleware' => [ - 'guest', - 'csrf' - ] - ], function () use ($router) { - - // Display Login Page - $router->get('login', [ - 'as' => 'auth.login', - 'uses' => 'Auth\LoginController@showLoginForm' - ]); - - // Handle Login - $router->post('login', [ - 'uses' => 'Auth\LoginController@login' - ]); - - // Determine if we need to ask for a TOTP Token - $router->post('login/totp', [ - 'uses' => 'Auth\LoginController@checkTotp' - ]); - - // Show Password Reset Form - $router->get('password', [ - 'as' => 'auth.password', - 'uses' => 'Auth\ForgotPasswordController@showLinkRequestForm' - ]); - - // Handle Password Reset - $router->post('password', [ - 'uses' => 'Auth\ForgotPasswordController@sendResetLinkEmail' - ]); - - // Show Verification Checkpoint - $router->get('password/reset/{token}', [ - 'as' => 'auth.reset', - 'uses' => 'Auth\ResetPasswordController@showResetForm' - ]); - - // Handle Verification - $router->post('password/reset', [ - 'uses' => 'Auth\ResetPasswordController@reset' - ]); - - }); - - // Not included above because we don't want the guest middleware - $router->get('auth/logout', [ - 'as' => 'auth.logout', - 'middleware' => 'auth', - 'uses' => 'Auth\LoginController@logout' - ]); - - } - -} diff --git a/app/Http/Routes/BaseRoutes.php b/app/Http/Routes/BaseRoutes.php deleted file mode 100644 index 6841ea1b6..000000000 --- a/app/Http/Routes/BaseRoutes.php +++ /dev/null @@ -1,126 +0,0 @@ - - * Some Modifications (c) 2015 Dylan Seidt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Routes; - -use Illuminate\Routing\Router; - -class BaseRoutes { - - public function map(Router $router) { - - // Index of Panel - $router->get('/', [ - 'as' => 'index', - 'middleware' => 'auth', - 'uses' => 'Base\IndexController@getIndex' - ]); - - // Handle Index. Redirect /index to / - $router->get('/index', function () { - return redirect()->route('index'); - }); - - // Password Generation - $router->get('/password-gen/{length}', [ - 'as' => 'password-gen', - 'middleware' => 'auth', - 'uses' => 'Base\IndexController@getPassword' - ]); - - // Account Routes - $router->group([ - 'prefix' => 'account', - 'middleware' => [ - 'auth', - 'csrf' - ] - ], function () use ($router) { - $router->get('/', [ - 'as' => 'account', - 'uses' => 'Base\AccountController@index' - ]); - $router->post('/password', [ - 'uses' => 'Base\AccountController@password' - ]); - $router->post('/email', [ - 'uses' => 'Base\AccountController@email' - ]); - }); - - // API Management Routes - $router->group([ - 'prefix' => 'account/api', - 'middleware' => [ - 'auth', - 'csrf' - ] - ], function () use ($router) { - $router->get('/', [ - 'as' => 'account.api', - 'uses' => 'Base\APIController@index' - ]); - $router->get('/new', [ - 'as' => 'account.api.new', - 'uses' => 'Base\APIController@new' - ]); - $router->post('/new', [ - 'uses' => 'Base\APIController@save' - ]); - - $router->delete('/revoke/{key}', [ - 'uses' => 'Base\APIController@revoke' - ]); - }); - - // TOTP Routes - $router->group([ - 'prefix' => 'account/security', - 'middleware' => [ - 'auth', - 'csrf' - ] - ], function () use ($router) { - $router->get('/', [ - 'as' => 'account.security', - 'uses' => 'Base\SecurityController@index' - ]); - $router->get('/revoke/{id}', [ - 'as' => 'account.security.revoke', - 'uses' => 'Base\SecurityController@revoke' - ]); - $router->put('/totp', [ - 'uses' => 'Base\SecurityController@generateTotp' - ]); - $router->post('/totp', [ - 'uses' => 'Base\SecurityController@setTotp' - ]); - $router->delete('/totp', [ - 'uses' => 'Base\SecurityController@disableTotp' - ]); - }); - - } - -} diff --git a/app/Http/Routes/RemoteRoutes.php b/app/Http/Routes/RemoteRoutes.php deleted file mode 100644 index 802f1d923..000000000 --- a/app/Http/Routes/RemoteRoutes.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Routes; - -use Illuminate\Routing\Router; -use Request; - -class RemoteRoutes { - - public function map(Router $router) { - $router->group(['prefix' => 'remote'], function () use ($router) { - // Handles Remote Download Authentication Requests - $router->post('download', [ - 'as' => 'remote.download', - 'uses' => 'Remote\RemoteController@postDownload' - ]); - - $router->post('install', [ - 'as' => 'remote.install', - 'uses' => 'Remote\RemoteController@postInstall' - ]); - - $router->post('event', [ - 'as' => 'remote.event', - 'uses' => 'Remote\RemoteController@event' - ]); - }); - } - -} diff --git a/app/Http/Routes/ServerRoutes.php b/app/Http/Routes/ServerRoutes.php deleted file mode 100644 index 5916287e2..000000000 --- a/app/Http/Routes/ServerRoutes.php +++ /dev/null @@ -1,181 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Routes; - -use Illuminate\Routing\Router; - -class ServerRoutes { - - public function map(Router $router) { - - $router->group([ - 'prefix' => 'server/{server}', - 'middleware' => [ - 'auth', - 'server', - 'csrf' - ] - ], function ($server) use ($router) { - - // Index View for Server - $router->get('/', [ - 'as' => 'server.index', - 'uses' => 'Server\ServerController@getIndex' - ]); - - // Settings - $router->get('/settings', [ - 'as' => 'server.settings', - 'uses' => 'Server\ServerController@getSettings' - ]); - - $router->post('/settings/sftp', [ - 'as' => 'server.settings.sftp', - 'uses' => 'Server\ServerController@postSettingsSFTP' - ]); - - $router->post('/settings/startup', [ - 'as' => 'server.settings.startup', - 'uses' => 'Server\ServerController@postSettingsStartup' - ]); - - // File Manager Routes - $router->get('/files', [ - 'as' => 'server.files.index', - 'uses' => 'Server\ServerController@getFiles' - ]); - - $router->get('/files/edit/{file}', [ - 'as' => 'server.files.edit', - 'uses' => 'Server\ServerController@getEditFile' - ])->where('file', '.*'); - - $router->get('/files/download/{file}', [ - 'as' => 'server.files.download', - 'uses' => 'Server\ServerController@getDownloadFile' - ])->where('file', '.*'); - - $router->get('/files/add', [ - 'as' => 'server.files.add', - 'uses' => 'Server\ServerController@getAddFile' - ]); - - $router->post('files/directory-list', [ - 'as' => 'server.files.directory-list', - 'uses' => 'Server\AjaxController@postDirectoryList' - ]); - - $router->post('files/save', [ - 'as' => 'server.files.save', - 'uses' => 'Server\AjaxController@postSaveFile' - ]); - - // Sub-User Routes - $router->get('users', [ - 'as' => 'server.subusers', - 'uses' => 'Server\SubuserController@getIndex' - ]); - - $router->get('users/new', [ - 'as' => 'server.subusers.new', - 'uses' => 'Server\SubuserController@getNew' - ]); - - $router->post('users/new', [ - 'uses' => 'Server\SubuserController@postNew' - ]); - - $router->get('users/view/{id}', [ - 'as' => 'server.subusers.view', - 'uses' => 'Server\SubuserController@getView' - ]); - - $router->post('users/view/{id}', [ - 'uses' => 'Server\SubuserController@postView' - ]); - - $router->delete('users/delete/{id}', [ - 'uses' => 'Server\SubuserController@deleteSubuser' - ]); - - $router->get('tasks/', [ - 'as' => 'server.tasks', - 'uses' => 'Server\TaskController@getIndex' - ]); - - $router->get('tasks/view/{id}', [ - 'as' => 'server.tasks.view', - 'uses' => 'Server\TaskController@getView' - ]); - - $router->get('tasks/new', [ - 'as' => 'server.tasks.new', - 'uses' => 'Server\TaskController@getNew' - ]); - - $router->post('tasks/new', [ - 'uses' => 'Server\TaskController@postNew' - ]); - - $router->delete('tasks/delete/{id}', [ - 'as' => 'server.tasks.delete', - 'uses' => 'Server\TaskController@deleteTask' - ]); - - $router->post('tasks/toggle/{id}', [ - 'as' => 'server.tasks.toggle', - 'uses' => 'Server\TaskController@toggleTask' - ]); - - // Assorted AJAX Routes - $router->group(['prefix' => 'ajax'], function ($server) use ($router) { - // Returns Server Status - $router->get('status', [ - 'uses' => 'Server\AjaxController@getStatus' - ]); - - // Sets the Default Connection for the Server - $router->post('set-primary', [ - 'uses' => 'Server\AjaxController@postSetPrimary' - ]); - - $router->post('settings/reset-database-password', [ - 'as' => 'server.ajax.reset-database-password', - 'uses' => 'Server\AjaxController@postResetDatabasePassword' - ]); - }); - - // Assorted AJAX Routes - $router->group(['prefix' => 'js'], function ($server) use ($router) { - // Returns Server Status - $router->get('{folder}/{file}', [ - 'as' => 'server.js', - 'uses' => 'Server\ServerController@getJavascript' - ])->where('file', '.*'); - - }); - }); - } - -} diff --git a/app/Jobs/SendScheduledTask.php b/app/Jobs/SendScheduledTask.php index 96d4a669a..525b670df 100644 --- a/app/Jobs/SendScheduledTask.php +++ b/app/Jobs/SendScheduledTask.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,26 +21,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Jobs; -use Pterodactyl\Jobs\Job; +use Cron; +use Carbon; +use Pterodactyl\Models\Task; +use Pterodactyl\Models\TaskLog; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; - -use DB; -use Carbon; -use Cron; -use Pterodactyl\Models; -use Pterodactyl\Repositories\Daemon\CommandRepository; use Pterodactyl\Repositories\Daemon\PowerRepository; +use Pterodactyl\Repositories\Daemon\CommandRepository; class SendScheduledTask extends Job implements ShouldQueue { use InteractsWithQueue, SerializesModels; - protected $server; - + /** + * @var \Pterodactyl\Models\Task + */ protected $task; /** @@ -48,13 +48,12 @@ class SendScheduledTask extends Job implements ShouldQueue * * @return void */ - public function __construct(Models\Server $server, Models\Task $task) + public function __construct(Task $task) { - $this->server = $server; $this->task = $task; - $task->queued = 1; - $task->save(); + $this->task->queued = true; + $this->task->save(); } /** @@ -65,7 +64,7 @@ class SendScheduledTask extends Job implements ShouldQueue public function handle() { $time = Carbon::now(); - $log = new Models\TaskLog; + $log = new TaskLog; if ($this->attempts() >= 1) { // Just delete the job, we will attempt it again later anyways. @@ -74,24 +73,27 @@ class SendScheduledTask extends Job implements ShouldQueue try { if ($this->task->action === 'command') { - $repo = new CommandRepository($this->server); + $repo = new CommandRepository($this->task->server, $this->task->user); $response = $repo->send($this->task->data); - } else if ($this->task->action === 'power') { - $repo = new PowerRepository($this->server); + } elseif ($this->task->action === 'power') { + $repo = new PowerRepository($this->task->server, $this->task->user); $response = $repo->do($this->task->data); + } else { + throw new \Exception('Task type provided was not valid.'); } + $log->fill([ 'task_id' => $this->task->id, 'run_time' => $time, 'run_status' => 0, - 'response' => $response + 'response' => $response, ]); } catch (\Exception $ex) { $log->fill([ 'task_id' => $this->task->id, 'run_time' => $time, 'run_status' => 1, - 'response' => $ex->getMessage() + 'response' => $ex->getMessage(), ]); } finally { $cron = Cron::factory(sprintf('%s %s %s %s %s %s', @@ -105,7 +107,7 @@ class SendScheduledTask extends Job implements ShouldQueue $this->task->fill([ 'last_run' => $time, 'next_run' => $cron->getNextRunDate(), - 'queued' => 0 + 'queued' => false, ]); $this->task->save(); $log->save(); diff --git a/app/Models/APIKey.php b/app/Models/APIKey.php index ae40b2036..6ed73b7c2 100644 --- a/app/Models/APIKey.php +++ b/app/Models/APIKey.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,12 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class APIKey extends Model { + /** + * Public key defined length used in verification methods. + * + * @var int + */ + const PUBLIC_KEY_LEN = 16; /** * The table associated with the model. @@ -42,6 +49,15 @@ class APIKey extends Model */ protected $hidden = ['secret']; + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'allowed_ips' => 'json', + ]; + /** * Fields that are not mass assignable. * @@ -49,4 +65,13 @@ class APIKey extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; + /** + * Gets the permissions associated with a key. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function permissions() + { + return $this->hasMany(APIPermission::class, 'key_id'); + } } diff --git a/app/Models/APILog.php b/app/Models/APILog.php index 41a7f8e47..6d5f26fda 100644 --- a/app/Models/APILog.php +++ b/app/Models/APILog.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class APILog extends Model { - /** * The table associated with the model. * @@ -54,8 +54,7 @@ class APILog extends Model * * @var array */ - protected $casts = [ - 'authorized' => 'boolean' - ]; - + protected $casts = [ + 'authorized' => 'boolean', + ]; } diff --git a/app/Models/APIPermission.php b/app/Models/APIPermission.php index 99c564b2a..bfe2fc908 100644 --- a/app/Models/APIPermission.php +++ b/app/Models/APIPermission.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class APIPermission extends Model { - /** * The table associated with the model. * @@ -47,15 +47,85 @@ class APIPermission extends Model * * @var array */ - protected $casts = [ - 'key_id' => 'integer', - ]; + protected $casts = [ + 'key_id' => 'integer', + ]; /** * Disable timestamps for this table. * - * @var boolean + * @var bool */ public $timestamps = false; + /** + * List of permissions available for the API. + * + * @var array + */ + protected static $permissions = [ + // Items within this block are available to non-adminitrative users. + '_user' => [ + 'server' => [ + 'list', + 'view', + 'power', + 'command', + ], + ], + + // All other pemissions below are administrative actions. + 'server' => [ + 'list', + 'create', + 'view', + 'edit-details', + 'edit-container', + 'edit-build', + 'edit-startup', + 'suspend', + 'install', + 'rebuild', + 'delete', + ], + 'location' => [ + 'list', + ], + 'node' => [ + 'list', + 'view', + 'view-config', + 'create', + 'delete', + ], + 'user' => [ + 'list', + 'view', + 'create', + 'edit', + 'delete', + ], + 'service' => [ + 'list', + 'view', + ], + 'option' => [ + 'list', + 'view', + ], + 'pack' => [ + 'list', + 'view', + ], + ]; + + /** + * Return permissions for API. + * + * @return array + */ + public static function permissions() + { + return self::$permissions; + } } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 2eeff851d..d37e52a0e 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class Allocation extends Model { - /** * The table associated with the model. * @@ -42,15 +42,46 @@ class Allocation extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ + /** + * Cast values to correct type. + * + * @var array + */ protected $casts = [ - 'node' => 'integer', + 'node_id' => 'integer', 'port' => 'integer', - 'assigned_to' => 'integer', + 'server_id' => 'integer', ]; + /** + * Accessor to automatically provide the IP alias if defined. + * + * @param null|string $value + * @return string + */ + public function getAliasAttribute($value) + { + return (is_null($this->ip_alias)) ? $this->ip : $this->ip_alias; + } + + /** + * Accessor to quickly determine if this allocation has an alias. + * + * @param null|string $value + * @return bool + */ + public function getHasAliasAttribute($value) + { + return ! is_null($this->ip_alias); + } + + /** + * Gets information for the server associated with this allocation. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } } diff --git a/app/Models/ServiceOptions.php b/app/Models/Checksum.php similarity index 87% rename from app/Models/ServiceOptions.php rename to app/Models/Checksum.php index 393445a48..6db275624 100644 --- a/app/Models/ServiceOptions.php +++ b/app/Models/Checksum.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,19 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; -class ServiceOptions extends Model +class Checksum extends Model { - /** * The table associated with the model. * * @var string */ - protected $table = 'service_options'; + protected $table = 'checksums'; /** * Fields that are not mass assignable. @@ -47,8 +47,7 @@ class ServiceOptions extends Model * * @var array */ - protected $casts = [ - 'parent_service' => 'integer', - ]; - + protected $casts = [ + 'service' => 'integer', + ]; } diff --git a/app/Models/Database.php b/app/Models/Database.php index c682b296e..20ad3c1b0 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class Database extends Model { - /** * The table associated with the model. * @@ -43,20 +43,41 @@ class Database extends Model protected $hidden = ['password']; /** - * Fields that are not mass assignable. + * Fields that are mass assignable. * * @var array */ - protected $guarded = ['id', 'created_at', 'updated_at']; + protected $fillable = [ + 'server_id', 'database_host_id', 'database', 'username', 'remote', + ]; /** * Cast values to correct type. * * @var array */ - protected $casts = [ - 'server' => 'integer', - 'db_server' => 'integer', - ]; + protected $casts = [ + 'server_id' => 'integer', + 'database_host_id' => 'integer', + ]; + /** + * Gets the host database server associated with a database. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function host() + { + return $this->belongsTo(DatabaseHost::class, 'database_host_id'); + } + + /** + * Gets the server associated with a database. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } } diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php new file mode 100644 index 000000000..165a99c5a --- /dev/null +++ b/app/Models/DatabaseHost.php @@ -0,0 +1,106 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Models; + +use Crypt; +use Config; +use Illuminate\Database\Eloquent\Model; + +class DatabaseHost extends Model +{ + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'database_hosts'; + + /** + * The attributes excluded from the model's JSON form. + * + * @var array + */ + protected $hidden = ['password']; + + /** + * Fields that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'name', 'host', 'port', 'username', 'max_databases', 'node_id', + ]; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'id' => 'integer', + 'max_databases' => 'integer', + 'node_id' => 'integer', + ]; + + /** + * Sets the database connection name with the details of the host. + * + * @param string $connection + * @return void + */ + public function setDynamicConnection($connection = 'dynamic') + { + Config::set('database.connections.' . $connection, [ + 'driver' => 'mysql', + 'host' => $this->host, + 'port' => $this->port, + 'database' => 'mysql', + 'username' => $this->username, + 'password' => Crypt::decrypt($this->password), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + ]); + } + + /** + * Gets the node associated with a database host. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function node() + { + return $this->belongsTo(Node::class); + } + + /** + * Gets the databases assocaited with this host. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function databases() + { + return $this->hasMany(Database::class); + } +} diff --git a/app/Models/Location.php b/app/Models/Location.php index 9778283f9..f9ceec767 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class Location extends Model { - /** * The table associated with the model. * @@ -42,4 +42,23 @@ class Location extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; + /** + * Gets the nodes in a specificed location. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function nodes() + { + return $this->hasMany(Node::class); + } + + /** + * Gets the servers within a given location. + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function servers() + { + return $this->hasManyThrough(Server::class, Node::class); + } } diff --git a/app/Models/Node.php b/app/Models/Node.php index 7bfc37cec..9c454cfcb 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use GuzzleHttp\Client; use Illuminate\Database\Eloquent\Model; +use Illuminate\Notifications\Notifiable; +use Nicolaslopezj\Searchable\SearchableTrait; class Node extends Model { + use Notifiable, SearchableTrait; /** * The table associated with the model. @@ -43,80 +47,141 @@ class Node extends Model */ protected $hidden = ['daemonSecret']; - /** - * Cast values to correct type. - * - * @var array - */ + /** + * Cast values to correct type. + * + * @var array + */ protected $casts = [ 'public' => 'integer', - 'location' => 'integer', + 'location_id' => 'integer', 'memory' => 'integer', 'disk' => 'integer', 'daemonListen' => 'integer', 'daemonSFTP' => 'integer', + 'behind_proxy' => 'boolean', ]; /** - * Fields that are not mass assignable. + * Fields that are mass assignable. * * @var array */ - protected $guarded = ['id', 'created_at', 'updated_at']; + protected $fillable = [ + 'public', 'name', 'location_id', + 'fqdn', 'scheme', 'behind_proxy', + 'memory', 'memory_overallocate', 'disk', + 'disk_overallocate', 'upload_size', + 'daemonSecret', 'daemonBase', + 'daemonSFTP', 'daemonListen', + ]; /** + * Fields that are searchable. + * * @var array */ - protected static $guzzle = []; + protected $searchable = [ + 'columns' => [ + 'nodes.name' => 10, + 'nodes.fqdn' => 8, + 'locations.short' => 4, + 'locations.long' => 4, + ], + 'joins' => [ + 'locations' => ['locations.id', 'nodes.location_id'], + ], + ]; /** - * @var array - */ - protected static $nodes = []; - - /** - * Returns an instance of the database object for the requested node ID. + * Return an instance of the Guzzle client for this specific node. * - * @param int $id - * @return \Illuminate\Database\Eloquent\Model - */ - public static function getByID($id) - { - - // The Node is already cached. - if (array_key_exists($id, self::$nodes)) { - return self::$nodes[$id]; - } - - self::$nodes[$id] = Node::where('id', $id)->first(); - return self::$nodes[$id]; - - } - - /** - * Returns a Guzzle Client for the node in question. - * - * @param int $node + * @param array $headers * @return \GuzzleHttp\Client */ - public static function guzzleRequest($node) + public function guzzleClient($headers = []) { - - // The Guzzle Client is cached already. - if (array_key_exists($node, self::$guzzle)) { - return self::$guzzle[$node]; - } - - $nodeData = self::getByID($node); - - self::$guzzle[$node] = new Client([ - 'base_uri' => sprintf('%s://%s:%s/', $nodeData->scheme, $nodeData->fqdn, $nodeData->daemonListen), - 'timeout' => 5.0, - 'connect_timeout' => 3.0, + return new Client([ + 'base_uri' => sprintf('%s://%s:%s/', $this->scheme, $this->fqdn, $this->daemonListen), + 'timeout' => config('pterodactyl.guzzle.timeout'), + 'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'), + 'headers' => $headers, ]); - - return self::$guzzle[$node]; - } + /** + * Returns the configuration in JSON format. + * + * @param bool $pretty + * @return string + */ + public function getConfigurationAsJson($pretty = false) + { + $config = [ + 'web' => [ + 'host' => '0.0.0.0', + 'listen' => $this->daemonListen, + 'ssl' => [ + 'enabled' => (! $this->behind_proxy && $this->scheme === 'https'), + 'certificate' => '/etc/letsencrypt/live/' . $this->fqdn . '/fullchain.pem', + 'key' => '/etc/letsencrypt/live/' . $this->fqdn . '/privkey.pem', + ], + ], + 'docker' => [ + 'socket' => '/var/run/docker.sock', + 'autoupdate_images' => true, + ], + 'sftp' => [ + 'path' => $this->daemonBase, + 'port' => $this->daemonSFTP, + 'container' => 'ptdl-sftp', + ], + 'logger' => [ + 'path' => 'logs/', + 'src' => false, + 'level' => 'info', + 'period' => '1d', + 'count' => 3, + ], + 'remote' => [ + 'base' => route('index'), + ], + 'uploads' => [ + 'size_limit' => $this->upload_size, + ], + 'keys' => [$this->daemonSecret], + ]; + + return json_encode($config, ($pretty) ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES); + } + + /** + * Gets the location associated with a node. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function location() + { + return $this->belongsTo(Location::class); + } + + /** + * Gets the servers associated with a node. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class); + } + + /** + * Gets the allocations associated with a node. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function allocations() + { + return $this->hasMany(Allocation::class); + } } diff --git a/app/Models/Pack.php b/app/Models/Pack.php new file mode 100644 index 000000000..9cda19790 --- /dev/null +++ b/app/Models/Pack.php @@ -0,0 +1,125 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Models; + +use File; +use Storage; +use Illuminate\Database\Eloquent\Model; +use Nicolaslopezj\Searchable\SearchableTrait; + +class Pack extends Model +{ + use SearchableTrait; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'packs'; + + /** + * Fields that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'option_id', 'name', 'version', 'description', 'selectable', 'visible', 'locked', + ]; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'option_id' => 'integer', + 'selectable' => 'boolean', + 'visible' => 'boolean', + 'locked' => 'boolean', + ]; + + /** + * Parameters for search querying. + * + * @var array + */ + protected $searchable = [ + 'columns' => [ + 'packs.name' => 10, + 'packs.uuid' => 8, + 'service_options.name' => 6, + 'service_options.tag' => 5, + 'service_options.docker_image' => 5, + 'packs.version' => 2, + ], + 'joins' => [ + 'service_options' => ['packs.option_id', 'service_options.id'], + ], + ]; + + /** + * Returns all of the archived files for a given pack. + * + * @param bool $collection + * @return \Illuminate\Support\Collection|object + */ + public function files($collection = false) + { + $files = collect(Storage::files('packs/' . $this->uuid)); + + $files = $files->map(function ($item) { + $path = storage_path('app/' . $item); + + return (object) [ + 'name' => basename($item), + 'hash' => sha1_file($path), + 'size' => File::humanReadableSize($path), + ]; + }); + + return ($collection) ? $files : (object) $files->all(); + } + + /** + * Gets option associated with a service pack. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function option() + { + return $this->belongsTo(ServiceOption::class); + } + + /** + * Gets servers associated with a pack. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class); + } +} diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 5a59d579d..c126967ec 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,12 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class Permission extends Model { + /** + * Should timestamps be used on this model. + * + * @var bool + */ + public $timestamps = false; /** * The table associated with the model. @@ -47,19 +54,105 @@ class Permission extends Model * * @var array */ - protected $casts = [ - 'user_id' => 'integer', - 'server_id' => 'integer', - ]; + protected $casts = [ + 'subuser_id' => 'integer', + ]; + /** + * A list of all permissions available for a user. + * + * @var array + */ + protected static $permissions = [ + 'power' => [ + 'power-start' => 's:power:start', + 'power-stop' => 's:power:stop', + 'power-restart' => 's:power:restart', + 'power-kill' => 's:power:kill', + 'send-command' => 's:command', + ], + 'subuser' => [ + 'list-subusers' => null, + 'view-subuser' => null, + 'edit-subuser' => null, + 'create-subuser' => null, + 'delete-subuser' => null, + ], + 'server' => [ + 'set-connection' => null, + 'view-startup' => null, + 'edit-startup' => null, + ], + 'sftp' => [ + 'view-sftp' => null, + 'view-sftp-password' => null, + 'reset-sftp' => 's:set-password', + ], + 'file' => [ + 'list-files' => 's:files:get', + 'edit-files' => 's:files:read', + 'save-files' => 's:files:post', + 'move-files' => 's:files:move', + 'copy-files' => 's:files:copy', + 'compress-files' => 's:files:compress', + 'decompress-files' => 's:files:decompress', + 'create-files' => 's:files:create', + 'upload-files' => 's:files:upload', + 'delete-files' => 's:files:delete', + 'download-files' => null, + ], + 'task' => [ + 'list-tasks' => null, + 'view-task' => null, + 'toggle-task' => null, + 'queue-task' => null, + 'create-task' => null, + 'delete-task' => null, + ], + 'database' => [ + 'view-databases' => null, + 'reset-db-password' => null, + ], + ]; + + /** + * Return a collection of permissions available. + * + * @param array $single + * @return \Illuminate\Support\Collection|array + */ + public static function listPermissions($single = false) + { + if ($single) { + return collect(self::$permissions)->mapWithKeys(function ($item) { + return $item; + })->all(); + } + + return collect(self::$permissions); + } + + /** + * Find permission by permission node. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $permission + * @return \Illuminate\Database\Query\Builder + */ public function scopePermission($query, $permission) { return $query->where('permission', $permission); } - public function scopeServer($query, $server) + /** + * Filter permission by server. + * + * @param \Illuminate\Database\Query\Builder $query + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Database\Query\Builder + */ + public function scopeServer($query, Server $server) { return $query->where('server_id', $server->id); } - } diff --git a/app/Models/Server.php b/app/Models/Server.php index 9948c7a10..1a26243e9 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,19 +21,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Auth; -use Pterodactyl\Models\Subuser; +use Cache; +use Carbon; +use Schema; +use Javascript; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\SoftDeletes; - -use Pterodactyl\Exceptions\DisplayException; +use Illuminate\Notifications\Notifiable; +use Nicolaslopezj\Searchable\SearchableTrait; class Server extends Model { - - use SoftDeletes; + use Notifiable, SearchableTrait; /** * The table associated with the model. @@ -68,154 +70,277 @@ class Server extends Model * * @var array */ - protected $casts = [ - 'node' => 'integer', - 'suspended' => 'integer', - 'owner' => 'integer', - 'memory' => 'integer', - 'swap' => 'integer', - 'disk' => 'integer', - 'io' => 'integer', - 'cpu' => 'integer', - 'oom_disabled' => 'integer', - 'port' => 'integer', - 'service' => 'integer', - 'option' => 'integer', - 'installed' => 'integer', - ]; + protected $casts = [ + 'node_id' => 'integer', + 'skip_scripts' => 'boolean', + 'suspended' => 'integer', + 'owner_id' => 'integer', + 'memory' => 'integer', + 'swap' => 'integer', + 'disk' => 'integer', + 'io' => 'integer', + 'cpu' => 'integer', + 'oom_disabled' => 'integer', + 'allocation_id' => 'integer', + 'service_id' => 'integer', + 'option_id' => 'integer', + 'pack_id' => 'integer', + 'installed' => 'integer', + ]; /** + * Parameters for search querying. + * * @var array */ - protected static $serverUUIDInstance = []; - - /** - * @var mixed - */ - protected static $user; - - /** - * Constructor - */ - public function __construct() - { - parent::__construct(); - self::$user = Auth::user(); - } - - /** - * Determine if we need to change the server's daemonSecret value to - * match that of the user if they are a subuser. - * - * @param Illuminate\Database\Eloquent\Model\Server $server - * @return string - */ - public static function getUserDaemonSecret(Server $server) - { - - if (self::$user->id === $server->owner || self::$user->root_admin === 1) { - return $server->daemonSecret; - } - - $subuser = Subuser::where('server_id', $server->id)->where('user_id', self::$user->id)->first(); - - if (is_null($subuser)) { - return null; - } - - return $subuser->daemonSecret; - - } - - /** - * Returns array of all servers owned by the logged in user. - * Returns all users servers if user is a root admin. - * - * @return \Illuminate\Database\Eloquent\Collection - */ - public static function getUserServers($paginate = null) - { - - $query = self::select( - 'servers.*', - 'nodes.name as nodeName', - 'locations.short as a_locationShort', - 'allocations.ip', - 'allocations.ip_alias', - 'allocations.port', - 'services.name as a_serviceName', - 'service_options.name as a_serviceOptionName' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('locations', 'nodes.location', '=', 'locations.id') - ->join('services', 'servers.service', '=', 'services.id') - ->join('service_options', 'servers.option', '=', 'service_options.id') - ->join('allocations', 'servers.allocation', '=', 'allocations.id'); - - if (self::$user->root_admin !== 1) { - $query->whereIn('servers.id', Subuser::accessServers()); - } - - if (is_numeric($paginate)) { - return $query->paginate($paginate); - } - - return $query->get(); - - } + protected $searchable = [ + 'columns' => [ + 'servers.name' => 10, + 'servers.username' => 10, + 'servers.uuidShort' => 9, + 'servers.uuid' => 8, + 'packs.name' => 7, + 'users.email' => 6, + 'users.username' => 6, + 'nodes.name' => 2, + ], + 'joins' => [ + 'packs' => ['packs.id', 'servers.pack_id'], + 'users' => ['users.id', 'servers.owner_id'], + 'nodes' => ['nodes.id', 'servers.node_id'], + ], + ]; /** * Returns a single server specified by UUID. * DO NOT USE THIS TO MODIFY SERVER DETAILS OR SAVE THOSE DETAILS. * YOU WILL OVERWRITE THE SECRET KEY AND BREAK THINGS. * - * @param string $uuid The Short-UUID of the server to return an object about. - * @return \Illuminate\Database\Eloquent\Collection + * @param string $uuid + * @param array $with + * @param array $withCount + * @return \Pterodactyl\Models\Server + * @todo Remove $with and $withCount due to cache issues, they aren't used anyways. */ - public static function getByUUID($uuid) + public static function byUuid($uuid, array $with = [], array $withCount = []) { - - if (array_key_exists($uuid, self::$serverUUIDInstance)) { - return self::$serverUUIDInstance[$uuid]; + if (! Auth::check()) { + throw new \Exception('You must call Server:byUuid as an authenticated user.'); } - $query = self::select('servers.*', 'services.file as a_serviceFile') - ->join('services', 'services.id', '=', 'servers.service') - ->where('uuidShort', $uuid) - ->orWhere('uuid', $uuid); + // Results are cached because we call this functions a few times on page load. + $result = Cache::tags(['Model:Server', 'Model:Server:byUuid:' . $uuid])->remember('Model:Server:byUuid:' . $uuid . Auth::user()->uuid, Carbon::now()->addMinutes(15), function () use ($uuid) { + $query = self::with('service', 'node')->where(function ($q) use ($uuid) { + $q->where('uuidShort', $uuid)->orWhere('uuid', $uuid); + }); - if (self::$user->root_admin !== 1) { - $query->whereIn('servers.id', Subuser::accessServers()); + if (! Auth::user()->isRootAdmin()) { + $query->whereIn('id', Auth::user()->serverAccessArray()); + } + + return $query->first(); + }); + + if (! is_null($result)) { + $result->daemonSecret = Auth::user()->daemonToken($result); } - $result = $query->first(); - - if(!is_null($result)) { - $result->daemonSecret = self::getUserDaemonSecret($result); - } - - self::$serverUUIDInstance[$uuid] = $result; - return self::$serverUUIDInstance[$uuid]; - + return $result; } /** - * Returns non-administrative headers for accessing a server on Scales + * Returns non-administrative headers for accessing a server on the daemon. * - * @param string $uuid + * @param Pterodactyl\Models\User|null $user * @return array */ - public static function getGuzzleHeaders($uuid) + public function guzzleHeaders(User $user = null) { - - if (array_key_exists($uuid, self::$serverUUIDInstance)) { - return [ - 'X-Access-Server' => self::$serverUUIDInstance[$uuid]->uuid, - 'X-Access-Token' => self::$serverUUIDInstance[$uuid]->daemonSecret - ]; + // If no specific user is passed, see if we can find an active + // auth session to pull data from. + if (is_null($user) && Auth::check()) { + $user = Auth::user(); } - return []; - + return [ + 'X-Access-Server' => $this->uuid, + 'X-Access-Token' => ($user) ? $user->daemonToken($this) : $this->daemonSecret, + ]; } + /** + * Return an instance of the Guzzle client for this specific server using defined access token. + * + * @param Pterodactyl\Models\User|null $user + * @return \GuzzleHttp\Client + */ + public function guzzleClient(User $user = null) + { + return $this->node->guzzleClient($this->guzzleHeaders($user)); + } + + /** + * Returns javascript object to be embedded on server view pages with relevant information. + * + * @param array|null $additional + * @param array|null $overwrite + * @return \Laracasts\Utilities\JavaScript\JavaScriptFacade + */ + public function js($additional = null, $overwrite = null) + { + $response = [ + 'server' => collect($this->makeVisible('daemonSecret'))->only([ + 'uuid', + 'uuidShort', + 'daemonSecret', + 'username', + ]), + 'node' => collect($this->node)->only([ + 'fqdn', + 'scheme', + 'daemonListen', + ]), + ]; + + if (is_array($additional)) { + $response = array_merge($response, $additional); + } + + if (is_array($overwrite)) { + $response = $overwrite; + } + + return Javascript::put($response); + } + + /** + * Return the columns available for this table. + * + * @return array + */ + public function getTableColumns() + { + return Schema::getColumnListing($this->getTable()); + } + + /** + * Gets the user who owns the server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class, 'owner_id'); + } + + /** + * Gets the subusers associated with a server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function subusers() + { + return $this->hasMany(Subuser::class); + } + + /** + * Gets the default allocation for a server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function allocation() + { + return $this->hasOne(Allocation::class, 'id', 'allocation_id'); + } + + /** + * Gets all allocations associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function allocations() + { + return $this->hasMany(Allocation::class, 'server_id'); + } + + /** + * Gets information for the pack associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function pack() + { + return $this->belongsTo(Pack::class); + } + + /** + * Gets information for the service associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function service() + { + return $this->belongsTo(Service::class); + } + + /** + * Gets information for the service option associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function option() + { + return $this->belongsTo(ServiceOption::class); + } + + /** + * Gets information for the service variables associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function variables() + { + return $this->hasMany(ServerVariable::class); + } + + /** + * Gets information for the node associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function node() + { + return $this->belongsTo(Node::class); + } + + /** + * Gets information for the tasks associated with this server. + * + * @TODO adjust server column in tasks to be server_id + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function tasks() + { + return $this->hasMany(Task::class); + } + + /** + * Gets all databases associated with a server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function databases() + { + return $this->hasMany(Database::class); + } + + /** + * Gets the location of the server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function location() + { + return $this->node->location(); + } } diff --git a/app/Models/DatabaseServer.php b/app/Models/ServerVariable.php similarity index 56% rename from app/Models/DatabaseServer.php rename to app/Models/ServerVariable.php index 78b45d953..165d5b3df 100644 --- a/app/Models/DatabaseServer.php +++ b/app/Models/ServerVariable.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,26 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; -class DatabaseServer extends Model +class ServerVariable extends Model { - /** * The table associated with the model. * * @var string */ - protected $table = 'database_servers'; - - /** - * The attributes excluded from the model's JSON form. - * - * @var array - */ - protected $hidden = ['password']; + protected $table = 'server_variables'; /** * Fields that are not mass assignable. @@ -49,15 +42,53 @@ class DatabaseServer extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ + /** + * Cast values to correct type. + * + * @var array + */ protected $casts = [ - 'id' => 'integer', 'server_id' => 'integer', - 'db_server' => 'integer' + 'variable_id' => 'integer', ]; + /** + * Determine if variable is viewable by users. + * + * @return bool + */ + public function getUserCanViewAttribute() + { + return (bool) $this->variable->user_viewable; + } + + /** + * Determine if variable is editable by users. + * + * @return bool + */ + public function getUserCanEditAttribute() + { + return (bool) $this->variable->user_editable; + } + + /** + * Determine if variable is required. + * + * @return bool + */ + public function getRequiredAttribute() + { + return $this->variable->required; + } + + /** + * Returns information about a given variables parent. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function variable() + { + return $this->belongsTo(ServiceVariable::class, 'variable_id'); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index e1d3e23e6..b05366882 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class Service extends Model { - /** * The table associated with the model. * @@ -36,10 +36,87 @@ class Service extends Model protected $table = 'services'; /** - * Fields that are not mass assignable. + * Fields that are mass assignable. * * @var array */ - protected $guarded = ['id', 'created_at', 'updated_at']; + protected $fillable = [ + 'name', 'description', 'folder', 'startup', 'index_file', + ]; + /** + * Returns the default contents of the index.js file for a service. + * + * @return string + */ + public static function defaultIndexFile() + { + return <<<'EOF' +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2017 Dane Everitt + * + * 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. + */ +const rfr = require('rfr'); +const _ = require('lodash'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service; +EOF; + } + + /** + * Gets all service options associated with this service. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function options() + { + return $this->hasMany(ServiceOption::class); + } + + /** + * Returns all of the packs associated with a service, regardless of the service option. + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function packs() + { + return $this->hasManyThrough( + 'Pterodactyl\Models\Pack', 'Pterodactyl\Models\ServiceOption', + 'service_id', 'option_id' + ); + } + + /** + * Gets all servers associated with this service. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class); + } } diff --git a/app/Models/ServiceOption.php b/app/Models/ServiceOption.php new file mode 100644 index 000000000..5f0cd9c9e --- /dev/null +++ b/app/Models/ServiceOption.php @@ -0,0 +1,143 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Models; + +use Illuminate\Database\Eloquent\Model; + +class ServiceOption extends Model +{ + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'service_options'; + + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'service_id' => 'integer', + 'script_is_privileged' => 'boolean', + ]; + + /** + * Returns the display startup string for the option and will use the parent + * service one if the option does not have one defined. + * + * @return string + */ + public function getDisplayStartupAttribute($value) + { + return (is_null($this->startup)) ? $this->service->startup : $this->startup; + } + + /** + * Returns the install script for the option; if option is copying from another + * it will return the copied script. + * + * @return string + */ + public function getCopyScriptInstallAttribute($value) + { + return (is_null($this->copy_script_from)) ? $this->script_install : $this->copyFrom->script_install; + } + + /** + * Returns the entry command for the option; if option is copying from another + * it will return the copied entry command. + * + * @return string + */ + public function getCopyScriptEntryAttribute($value) + { + return (is_null($this->copy_script_from)) ? $this->script_entry : $this->copyFrom->script_entry; + } + + /** + * Returns the install container for the option; if option is copying from another + * it will return the copied install container. + * + * @return string + */ + public function getCopyScriptContainerAttribute($value) + { + return (is_null($this->copy_script_from)) ? $this->script_container : $this->copyFrom->script_container; + } + + /** + * Gets service associated with a service option. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function service() + { + return $this->belongsTo(Service::class); + } + + /** + * Gets all servers associated with this service option. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class, 'option_id'); + } + + /** + * Gets all variables associated with this service. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function variables() + { + return $this->hasMany(ServiceVariable::class, 'option_id'); + } + + /** + * Gets all packs associated with this service. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function packs() + { + return $this->hasMany(Pack::class, 'option_id'); + } + + public function copyFrom() + { + return $this->belongsTo(self::class, 'copy_script_from'); + } +} diff --git a/app/Models/ServiceVariables.php b/app/Models/ServiceVariable.php similarity index 54% rename from app/Models/ServiceVariables.php rename to app/Models/ServiceVariable.php index 8b7f203b3..93e93e7e9 100644 --- a/app/Models/ServiceVariables.php +++ b/app/Models/ServiceVariable.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; -class ServiceVariables extends Model +class ServiceVariable extends Model { - /** * The table associated with the model. * @@ -47,11 +47,54 @@ class ServiceVariables extends Model * * @var array */ - protected $casts = [ - 'option_id' => 'integer', - 'user_viewable' => 'integer', - 'user_editable' => 'integer', - 'required' => 'integer', - ]; + protected $casts = [ + 'option_id' => 'integer', + 'user_viewable' => 'integer', + 'user_editable' => 'integer', + ]; + /** + * Reserved environment variable names. + * + * @var array + */ + protected static $reservedNames = [ + 'SERVER_MEMORY', + 'SERVER_IP', + 'SERVER_PORT', + 'ENV', + 'HOME', + 'USER', + ]; + + /** + * Returns an array of environment variable names that cannot be used. + * + * @return array + */ + public static function reservedNames() + { + return self::$reservedNames; + } + + /** + * Returns the display executable for the option and will use the parent + * service one if the option does not have one defined. + * + * @return bool + */ + public function getRequiredAttribute($value) + { + return $this->rules === 'required' || str_contains($this->rules, ['required|', '|required']); + } + + /** + * Return server variables associated with this variable. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function serverVariable() + { + return $this->hasMany(ServerVariable::class, 'variable_id'); + } } diff --git a/app/Models/Session.php b/app/Models/Session.php index 2b17bf6ce..4794d8e04 100644 --- a/app/Models/Session.php +++ b/app/Models/Session.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class Session extends Model { - /** * The table associated with the model. * @@ -40,9 +40,8 @@ class Session extends Model * * @var array */ - protected $casts = [ - 'id' => 'string', - 'user_id' => 'integer', - ]; - + protected $casts = [ + 'id' => 'string', + 'user_id' => 'integer', + ]; } diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index 5c22adfb7..a2eff9c9a 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; -use Auth; use Illuminate\Database\Eloquent\Model; +use Illuminate\Notifications\Notifiable; class Subuser extends Model { + use Notifiable; /** * The table associated with the model. @@ -55,43 +57,38 @@ class Subuser extends Model * * @var array */ - protected $casts = [ - 'user_id' => 'integer', - 'server_id' => 'integer', - ]; + protected $casts = [ + 'user_id' => 'integer', + 'server_id' => 'integer', + ]; /** - * @var mixed - */ - protected static $user; - - /** - * Constructor - */ - public function __construct() - { - self::$user = Auth::user(); - } - - /** - * Returns an array of each server ID that the user has access to. + * Gets the server associated with a subuser. * - * @return array + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public static function accessServers() + public function server() { - - $access = []; - - $union = self::select('server_id')->where('user_id', self::$user->id); - $select = Server::select('id')->where('owner', self::$user->id)->union($union)->get(); - - foreach($select as &$select) { - $access = array_merge($access, [ $select->id ]); - } - - return $access; - + return $this->belongsTo(Server::class); } + /** + * Gets the user associated with a subuser. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class); + } + + /** + * Gets the permissions associated with a subuser. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function permissions() + { + return $this->hasMany(Permission::class); + } } diff --git a/app/Models/Task.php b/app/Models/Task.php index cfac1f5b7..5e44a8264 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class Task extends Model { - /** * The table associated with the model. * @@ -47,17 +47,38 @@ class Task extends Model * * @var array */ - protected $casts = [ - 'id' => 'integer', - 'server' => 'integer', - 'queued' => 'integer', - ]; + protected $casts = [ + 'id' => 'integer', + 'user_id' => 'integer', + 'server_id' => 'integer', + 'queued' => 'boolean', + 'active' => 'boolean', + ]; - /** + /** * The attributes that should be mutated to dates. * * @var array */ protected $dates = ['last_run', 'next_run', 'created_at', 'updated_at']; + /** + * Gets the server associated with a task. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } + + /** + * Gets the user associated with a task. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class); + } } diff --git a/app/Models/TaskLog.php b/app/Models/TaskLog.php index d2e2519d1..578c625c5 100644 --- a/app/Models/TaskLog.php +++ b/app/Models/TaskLog.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; class TaskLog extends Model { - /** * The table associated with the model. * @@ -47,17 +47,16 @@ class TaskLog extends Model * * @var array */ - protected $casts = [ - 'id' => 'integer', - 'task_id' => 'integer', - 'run_status' => 'integer' - ]; + protected $casts = [ + 'id' => 'integer', + 'task_id' => 'integer', + 'run_status' => 'integer', + ]; - /** + /** * The attributes that should be mutated to dates. * * @var array */ protected $dates = ['run_time', 'created_at', 'updated_at']; - } diff --git a/app/Models/User.php b/app/Models/User.php index 990511ecb..12504b6b0 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,29 +21,47 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Models; use Hash; use Google2FA; -use Pterodactyl\Exceptions\AccountNotFoundException; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Models\Permission; -use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; - use Illuminate\Auth\Authenticatable; -use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Model; +use Illuminate\Notifications\Notifiable; +use Pterodactyl\Exceptions\DisplayException; +use Nicolaslopezj\Searchable\SearchableTrait; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; +use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; -class User extends Model implements AuthenticatableContract, - AuthorizableContract, - CanResetPasswordContract +class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { - use Authenticatable, Authorizable, CanResetPassword, Notifiable; + use Authenticatable, Authorizable, CanResetPassword, Notifiable, SearchableTrait; + + /** + * The rules for user passwords. + * + * @var string + */ + const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; + + /** + * The regex rules for usernames. + * + * @var string + */ + const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/'; + + /** + * Level of servers to display when using access() on a user. + * + * @var string + */ + protected $accessLevel = 'all'; /** * The table associated with the model. @@ -53,21 +71,22 @@ class User extends Model implements AuthenticatableContract, protected $table = 'users'; /** - * The attributes that are not mass assignable. + * A list of mass-assignable variables. * * @var array */ - protected $guarded = ['id', 'remeber_token', 'created_at', 'updated_at']; + protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar', 'root_admin']; /** * Cast values to correct type. * * @var array */ - protected $casts = [ - 'root_admin' => 'integer', - 'use_totp' => 'integer', - ]; + protected $casts = [ + 'root_admin' => 'integer', + 'use_totp' => 'integer', + 'gravatar' => 'integer', + ]; /** * The attributes excluded from the model's JSON form. @@ -77,35 +96,37 @@ class User extends Model implements AuthenticatableContract, protected $hidden = ['password', 'remember_token', 'totp_secret']; /** - * The rules for user passwords - * - * @var string - */ - const PASSWORD_RULES = 'min:8|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; + * Parameters for search querying. + * + * @var array + */ + protected $searchable = [ + 'columns' => [ + 'email' => 10, + 'username' => 9, + 'name_first' => 6, + 'name_last' => 6, + 'uuid' => 1, + ], + ]; - public function permissions() - { - return $this->hasMany(Permission::class); - } + protected $query; /** * Enables or disables TOTP on an account if the token is valid. * - * @param int $token The token that we want to verify. - * @return boolean + * @param int $token + * @return bool */ public function toggleTotp($token) { - - if (!Google2FA::verifyKey($this->totp_secret, $token)) { + if (! Google2FA::verifyKey($this->totp_secret, $token, 1)) { return false; } - $this->use_totp = !$this->use_totp; - $this->save(); - - return true; + $this->use_totp = ! $this->use_totp; + return $this->save(); } /** @@ -113,24 +134,20 @@ class User extends Model implements AuthenticatableContract, * - 8 or more characters in length * - at least one uppercase character * - at least one lowercase character - * - at least one number + * - at least one number. * - * @param string $password The raw password to set the account password to. - * @param string $regex The regex to use when validating the password. Defaults to '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'. + * @param string $password + * @param string $regex * @return void */ public function setPassword($password, $regex = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})') { - - if (!preg_match($regex, $password)) { + if (! preg_match($regex, $password)) { throw new DisplayException('The password passed did not meet the minimum password requirements.'); } $this->password = Hash::make($password); $this->save(); - - return; - } /** @@ -144,4 +161,128 @@ class User extends Model implements AuthenticatableContract, $this->notify(new ResetPasswordNotification($token)); } + /** + * Return true or false depending on wether the user is root admin or not. + * + * @return bool + */ + public function isRootAdmin() + { + return $this->root_admin === 1; + } + + /** + * Returns the user's daemon secret for a given server. + * + * @param \Pterodactyl\Models\Server $server + * @return null|string + */ + public function daemonToken(Server $server) + { + if ($this->id === $server->owner_id || $this->isRootAdmin()) { + return $server->daemonSecret; + } + + $subuser = $this->subuserOf->where('server_id', $server->id)->first(); + + return ($subuser) ? $subuser->daemonSecret : null; + } + + /** + * Returns an array of all servers a user is able to access. + * Note: does not account for user admin status. + * + * @return array + */ + public function serverAccessArray() + { + return Server::select('id')->where('owner_id', $this->id)->union( + Subuser::select('server_id')->where('user_id', $this->id) + )->pluck('id')->all(); + } + + /** + * Change the access level for a given call to `access()` on the user. + * + * @param string $level can be all, admin, subuser, owner + * @return void + */ + public function setAccessLevel($level = 'all') + { + if (! in_array($level, ['all', 'admin', 'subuser', 'owner'])) { + $level = 'all'; + } + $this->accessLevel = $level; + + return $this; + } + + /** + * Returns an array of all servers a user is able to access. + * Note: does not account for user admin status. + * + * @param array $load + * @return \Illuiminate\Database\Eloquent\Builder + */ + public function access(...$load) + { + if (count($load) > 0 && is_null($load[0])) { + $query = Server::query(); + } else { + $query = Server::with(! empty($load) ? $load : ['service', 'node', 'allocation']); + } + + // If access level is set to owner, only display servers + // that the user owns. + if ($this->accessLevel === 'owner') { + $query->where('owner_id', $this->id); + } + + // If set to all, display all servers they can access, including + // those they access as an admin. + // + // If set to subuser, only return the servers they can access because + // they are owner, or marked as a subuser of the server. + if (($this->accessLevel === 'all' && ! $this->isRootAdmin()) || $this->accessLevel === 'subuser') { + $query->whereIn('id', $this->serverAccessArray()); + } + + // If set to admin, only display the servers a user can access + // as an administrator (leaves out owned and subuser of). + if ($this->accessLevel === 'admin' && $this->isRootAdmin()) { + $query->whereNotIn('id', $this->serverAccessArray()); + } + + return $query; + } + + /** + * Returns all permissions that a user has. + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function permissions() + { + return $this->hasManyThrough(Permission::class, Subuser::class); + } + + /** + * Returns all servers that a user owns. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class, 'owner_id'); + } + + /** + * Return all servers that user is listed as a subuser of directly. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function subuserOf() + { + return $this->hasMany(Subuser::class); + } } diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index 806aa9408..f92a7a477 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Notifications; use Illuminate\Bus\Queueable; @@ -35,18 +36,19 @@ class AccountCreated extends Notification implements ShouldQueue /** * The password reset token to send. * - * @var string + * @var object */ - public $token; + public $user; /** * Create a new notification instance. * + * @param aray $user * @return void */ - public function __construct($token) + public function __construct(array $user) { - $this->token = $token; + $this->user = (object) $user; } /** @@ -68,10 +70,16 @@ class AccountCreated extends Notification implements ShouldQueue */ public function toMail($notifiable) { - return (new MailMessage) - ->line('You are recieving this email because an account has been created for you on Pterodactyl Panel.') - ->line('Email: ' . $notifiable->email) - ->action('Setup Your Account', url('/auth/password/reset/' . $this->token . '?email=' . $notifiable->email)); - } + $message = (new MailMessage) + ->greeting('Hello ' . $this->user->name . '!') + ->line('You are recieving this email because an account has been created for you on Pterodactyl Panel.') + ->line('Username: ' . $this->user->username) + ->line('Email: ' . $notifiable->email); + if (! is_null($this->user->token)) { + return $message->action('Setup Your Account', url('/auth/password/reset/' . $this->user->token . '?email=' . $notifiable->email)); + } + + return $message; + } } diff --git a/app/Jobs/SuspendServer.php b/app/Notifications/AddedToServer.php similarity index 51% rename from app/Jobs/SuspendServer.php rename to app/Notifications/AddedToServer.php index 912f8a0a9..415b39fb0 100644 --- a/app/Jobs/SuspendServer.php +++ b/app/Notifications/AddedToServer.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,45 +21,57 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Jobs; -use Debugbar; +namespace Pterodactyl\Notifications; + use Illuminate\Bus\Queueable; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Notifications\Messages\MailMessage; -use Pterodactyl\Repositories\ServerRepository; - -class SuspendServer extends Job implements ShouldQueue +class AddedToServer extends Notification implements ShouldQueue { - use InteractsWithQueue, SerializesModels; + use Queueable; /** - * ID of associated server model. * @var object */ - protected $id; + public $server; /** - * Create a new job instance. + * Create a new notification instance. * - * @param integer $id + * @param array $server * @return void */ - public function __construct($id) + public function __construct(array $server) { - $this->id = $id; + $this->server = (object) $server; } /** - * Execute the job. + * Get the notification's delivery channels. * - * @return void + * @param mixed $notifiable + * @return array */ - public function handle() + public function via($notifiable) { - $repo = new ServerRepository; - $repo->suspend($this->id, true); + return ['mail']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->greeting('Hello ' . $this->server->user . '!') + ->line('You have been added as a subuser for the following server, allowing you certain control over the server.') + ->line('Server Name: ' . $this->server->name) + ->action('Visit Server', route('server.index', $this->server->uuidShort)); } } diff --git a/app/Jobs/DeleteServer.php b/app/Notifications/RemovedFromServer.php similarity index 53% rename from app/Jobs/DeleteServer.php rename to app/Notifications/RemovedFromServer.php index 78e1e1f48..d4831dbe4 100644 --- a/app/Jobs/DeleteServer.php +++ b/app/Notifications/RemovedFromServer.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,47 +21,58 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Jobs; -use DB; +namespace Pterodactyl\Notifications; use Illuminate\Bus\Queueable; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Notifications\Messages\MailMessage; -use Pterodactyl\Models; -use Pterodactyl\Repositories\ServerRepository; - -class DeleteServer extends Job implements ShouldQueue +class RemovedFromServer extends Notification implements ShouldQueue { - use InteractsWithQueue, SerializesModels; + use Queueable; /** - * Id of server to be deleted. * @var object */ - protected $id; + public $server; /** - * Create a new job instance. + * Create a new notification instance. * - * @param integer $server + * @param array $server * @return void */ - public function __construct($id) + public function __construct(array $server) { - $this->id = $id; + $this->server = (object) $server; } /** - * Execute the job. + * Get the notification's delivery channels. * - * @return void + * @param mixed $notifiable + * @return array */ - public function handle() + public function via($notifiable) { - $repo = new ServerRepository; - $repo->deleteNow($this->id); + return ['mail']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->error() + ->greeting('Hello ' . $this->server->user . '.') + ->line('You have been removed as a subuser for the following server.') + ->line('Server Name: ' . $this->server->name) + ->action('Visit Panel', route('index')); } } diff --git a/app/Notifications/SendPasswordReset.php b/app/Notifications/SendPasswordReset.php index b7aa49ec0..367f863ed 100644 --- a/app/Notifications/SendPasswordReset.php +++ b/app/Notifications/SendPasswordReset.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Notifications; use Illuminate\Bus\Queueable; @@ -42,6 +43,7 @@ class SendPasswordReset extends Notification implements ShouldQueue /** * Create a new notification instance. * + * @param string $token * @return void */ public function __construct($token) @@ -71,20 +73,7 @@ class SendPasswordReset extends Notification implements ShouldQueue return (new MailMessage) ->subject('Reset Password') ->line('You are receiving this email because we received a password reset request for your account.') - ->action('Reset Password', url('auth/password/reset', $this->token)) + ->action('Reset Password', url('/auth/password/reset/' . $this->token . '?email=' . $notifiable->email)) ->line('If you did not request a password reset, no further action is required.'); } - - /** - * Get the array representation of the notification. - * - * @param mixed $notifiable - * @return array - */ - public function toArray($notifiable) - { - return [ - // - ]; - } } diff --git a/app/Notifications/ServerCreated.php b/app/Notifications/ServerCreated.php index 3d2b21344..9f881729a 100644 --- a/app/Notifications/ServerCreated.php +++ b/app/Notifications/ServerCreated.php @@ -1,4 +1,26 @@ . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ namespace Pterodactyl\Notifications; @@ -11,6 +33,9 @@ class ServerCreated extends Notification implements ShouldQueue { use Queueable; + /** + * @var object + */ public $server; /** @@ -44,13 +69,12 @@ class ServerCreated extends Notification implements ShouldQueue public function toMail($notifiable) { return (new MailMessage) - ->line('A new server as been assigned to your account.') - ->line('Server Name: ' . $this->server->name) - ->line('Memory: ' . $this->server->memory . ' MB') - ->line('Node: ' . $this->server->node) - ->line('Type: ' . $this->server->service . ' - ' . $this->server->option) - ->action('Peel Off the Protective Wrap', route('server.index', $this->server->uuidShort)) - ->line('Please let us know if you have any additional questions or concerns!'); + ->line('A new server as been assigned to your account.') + ->line('Server Name: ' . $this->server->name) + ->line('Memory: ' . $this->server->memory . ' MB') + ->line('Node: ' . $this->server->node) + ->line('Type: ' . $this->server->service . ' - ' . $this->server->option) + ->action('Peel Off the Protective Wrap', route('server.index', $this->server->uuidShort)) + ->line('Please let us know if you have any additional questions or concerns!'); } - } diff --git a/app/Observers/ServerObserver.php b/app/Observers/ServerObserver.php new file mode 100644 index 000000000..cd8c2187a --- /dev/null +++ b/app/Observers/ServerObserver.php @@ -0,0 +1,145 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Observers; + +use Cache; +use Pterodactyl\Events; +use Pterodactyl\Models\Server; +use Pterodactyl\Notifications\ServerCreated; +use Illuminate\Foundation\Bus\DispatchesJobs; + +class ServerObserver +{ + use DispatchesJobs; + + /** + * Listen to the Server creating event. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function creating(Server $server) + { + event(new Events\Server\Creating($server)); + } + + /** + * Listen to the Server created event. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function created(Server $server) + { + event(new Events\Server\Created($server)); + + // Queue Notification Email + $server->user->notify((new ServerCreated([ + 'name' => $server->name, + 'memory' => $server->memory, + 'node' => $server->node->name, + 'service' => $server->service->name, + 'option' => $server->option->name, + 'uuidShort' => $server->uuidShort, + ]))); + } + + /** + * Listen to the Server deleting event. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function deleting(Server $server) + { + event(new Events\Server\Deleting($server)); + } + + /** + * Listen to the Server deleted event. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function deleted(Server $server) + { + event(new Events\Server\Deleted($server)); + } + + /** + * Listen to the Server saving event. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function saving(Server $server) + { + event(new Events\Server\Saving($server)); + } + + /** + * Listen to the Server saved event. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function saved(Server $server) + { + event(new Events\Server\Saved($server)); + } + + /** + * Listen to the Server updating event. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function updating(Server $server) + { + event(new Events\Server\Updating($server)); + } + + /** + * Listen to the Server saved event. + * + * @param \Pterodactyl\Models\Server $server + * @return void + */ + public function updated(Server $server) + { + /* + * The cached byUuid model calls are tagged with Model:Server:byUuid: + * so that they can be accessed regardless of if there is an Auth::user() + * defined or not. + * + * We can also delete all cached byUuid items using the Model:Server tag. + */ + Cache::tags('Model:Server:byUuid:' . $server->uuid)->flush(); + Cache::tags('Model:Server:byUuid:' . $server->uuidShort)->flush(); + Cache::tags('Downloads:Server:' . $server->uuid)->flush(); + + event(new Events\Server\Updated($server)); + } +} diff --git a/app/Observers/SubuserObserver.php b/app/Observers/SubuserObserver.php new file mode 100644 index 000000000..57eab2680 --- /dev/null +++ b/app/Observers/SubuserObserver.php @@ -0,0 +1,88 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Observers; + +use Pterodactyl\Events; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Notifications\AddedToServer; +use Pterodactyl\Notifications\RemovedFromServer; + +class SubuserObserver +{ + /** + * Listen to the Subuser creating event. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @return void + */ + public function creating(Subuser $subuser) + { + event(new Events\Subuser\Creating($subuser)); + } + + /** + * Listen to the Subuser created event. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @return void + */ + public function created(Subuser $subuser) + { + event(new Events\Subuser\Created($subuser)); + + $subuser->user->notify((new AddedToServer([ + 'user' => $subuser->user->name_first, + 'name' => $subuser->server->name, + 'uuidShort' => $subuser->server->uuidShort, + ]))); + } + + /** + * Listen to the Subuser deleting event. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @return void + */ + public function deleting(Subuser $subuser) + { + event(new Events\Subuser\Deleting($subuser)); + } + + /** + * Listen to the Subuser deleted event. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @return void + */ + public function deleted(Subuser $subuser) + { + event(new Events\Subuser\Deleted($subuser)); + + $subuser->user->notify((new RemovedFromServer([ + 'user' => $subuser->user->name_first, + 'name' => $subuser->server->name, + ]))); + } +} diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php new file mode 100644 index 000000000..e7b07851d --- /dev/null +++ b/app/Observers/UserObserver.php @@ -0,0 +1,94 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Observers; + +use DB; +use Hash; +use Carbon; +use Pterodactyl\Events; +use Pterodactyl\Models\User; +use Pterodactyl\Notifications\AccountCreated; + +class UserObserver +{ + /** + * Listen to the User creating event. + * + * @param \Pterodactyl\Models\User $user + * @return void + */ + public function creating(User $user) + { + event(new Events\User\Creating($user)); + } + + /** + * Listen to the User created event. + * + * @param \Pterodactyl\Models\User $user + * @return void + */ + public function created(User $user) + { + event(new Events\User\Created($user)); + + if ($user->password === 'unset') { + $token = hash_hmac('sha256', str_random(40), config('app.key')); + DB::table('password_resets')->insert([ + 'email' => $user->email, + 'token' => Hash::make($token), + 'created_at' => Carbon::now()->toDateTimeString(), + ]); + } + + $user->notify(new AccountCreated([ + 'name' => $user->name_first, + 'username' => $user->username, + 'token' => (isset($token)) ? $token : null, + ])); + } + + /** + * Listen to the User deleting event. + * + * @param \Pterodactyl\Models\User $user + * @return void + */ + public function deleting(User $user) + { + event(new Events\User\Deleting($user)); + } + + /** + * Listen to the User deleted event. + * + * @param \Pterodactyl\Models\User $user + * @return void + */ + public function deleted(User $user) + { + event(new Events\User\Deleted($user)); + } +} diff --git a/app/Policies/APIKeyPolicy.php b/app/Policies/APIKeyPolicy.php new file mode 100644 index 000000000..95846b9e4 --- /dev/null +++ b/app/Policies/APIKeyPolicy.php @@ -0,0 +1,73 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Policies; + +use Cache; +use Carbon; +use Pterodactyl\Models\User; +use Pterodactyl\Models\APIKey as Key; +use Pterodactyl\Models\APIPermission as Permission; + +class APIKeyPolicy +{ + /** + * Checks if the API key has permission to perform an action. + * + * @param \Pterodactyl\Models\User $user + * @param \Pterodactyl\Models\APIKey $key + * @param string $permission + * @return bool + */ + protected function checkPermission(User $user, Key $key, $permission) + { + // Non-administrative users cannot use administrative routes. + if (! starts_with($key, 'user.') && ! $user->isRootAdmin()) { + return false; + } + + // We don't tag this cache key with the user uuid because the key is already unique, + // and multiple users are not defiend for a single key. + $permissions = Cache::remember('APIKeyPolicy.' . $key->public, Carbon::now()->addSeconds(5), function () use ($key) { + return $key->permissions()->get()->transform(function ($item) { + return $item->permission; + })->values(); + }); + + return $permissions->search($permission, true) !== false; + } + + /** + * Determine if a user has permission to perform this action against the system. + * + * @param \Pterodactyl\Models\User $user + * @param string $permission + * @param \Pterodactyl\Models\APIKey $key + * @return bool + */ + public function before(User $user, $permission, Key $key) + { + return $this->checkPermission($user, $key, $permission); + } +} diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index 2615149f0..56cd359e1 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,625 +21,49 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Policies; +use Cache; +use Carbon; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; class ServerPolicy { - /** - * Create a new policy instance. + * Checks if the user has the given permission on/for the server. * - * @return void + * @param \Pterodactyl\Models\User $user + * @param \Pterodactyl\Models\Server $server + * @param string $permission + * @return bool */ - public function __construct() + protected function checkPermission(User $user, Server $server, $permission) { - // - } + $permissions = Cache::remember('ServerPolicy.' . $user->uuid . $server->uuid, Carbon::now()->addSeconds(5), function () use ($user, $server) { + return $user->permissions()->server($server)->get()->transform(function ($item) { + return $item->permission; + })->values(); + }); - /** - * Determine if current user is the owner of a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - protected function isOwner(User $user, Server $server) - { - return $server->owner === $user->id; + return $permissions->search($permission, true) !== false; } /** * Runs before any of the functions are called. Used to determine if user is root admin, if so, ignore permissions. * - * @param Pterodactyl\Models\User $user - * @param string $ability - * @return boolean + * @param \Pterodactyl\Models\User $user + * @param string $ability + * @param \Pterodactyl\Models\Server $server + * @return bool */ - public function before(User $user, $ability) + public function before(User $user, $ability, Server $server) { - if ($user->root_admin === 1) { - return true; - } - } - - /** - * Check if user has permission to control power for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function power(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { + if ($user->isRootAdmin() || $server->owner_id === $user->id) { return true; } - return $user->permissions()->server($server)->permission('power')->exists(); + return $this->checkPermission($user, $server, $ability); } - - /** - * Check if user has permission to start a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function powerStart(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('power-start')->exists(); - } - - /** - * Check if user has permission to stop a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function powerStop(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('power-stop')->exists(); - } - - /** - * Check if user has permission to restart a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function powerRestart(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('power-restart')->exists(); - } - - /** - * Check if user has permission to kill a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function powerKill(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('power-kill')->exists(); - } - - /** - * Check if user has permission to run a command on a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function sendCommand(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('send-command')->exists(); - } - - /** - * Check if user has permission to list files on a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function listFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('list-files')->exists(); - } - - /** - * Check if user has permission to edit files on a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function editFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('edit-files')->exists(); - } - - /** - * Check if user has permission to save files on a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function saveFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('save-files')->exists(); - } - - /** - * Check if user has permission to move and rename files and folders on a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function moveFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('move-files')->exists(); - } - - /** - * Check if user has permission to copy folders and files on a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function copyFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('copy-files')->exists(); - } - - /** - * Check if user has permission to compress files and folders on a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function compressFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('compress-files')->exists(); - } - - /** - * Check if user has permission to decompress files on a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function decompressFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('decompress-files')->exists(); - } - - /** - * Check if user has permission to add files to a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function addFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('add-files')->exists(); - } - - /** - * Check if user has permission to upload files to a server. - * This permission relies on the user having the 'add-files' permission as well due to page authorization. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function uploadFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('upload-files')->exists(); - } - - /** - * Check if user has permission to download files from a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function downloadFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('download-files')->exists(); - } - - /** - * Check if user has permission to delete files from a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function deleteFiles(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('delete-files')->exists(); - } - - /** - * Check if user has permission to view subusers for the server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function listSubusers(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('list-subusers')->exists(); - } - - /** - * Check if user has permission to view specific subuser permissions. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function viewSubuser(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('view-subuser')->exists(); - } - - /** - * Check if user has permission to edit a subuser. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function editSubuser(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('edit-subuser')->exists(); - } - - /** - * Check if user has permission to delete a subuser. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function deleteSubuser(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('delete-subuser')->exists(); - } - - /** - * Check if user has permission to edit a subuser. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function createSubuser(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('create-subuser')->exists(); - } - - /** - * Check if user has permission to set the default connection for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function setConnection(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('set-connection')->exists(); - } - - /** - * Check if user has permission to view the startup command used for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function viewStartup(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('view-startup')->exists(); - } - - /** - * Check if user has permission to edit the startup command used for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function editStartup(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('edit-startup')->exists(); - } - - /** - * Check if user has permission to view the SFTP information for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function viewSftp(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('view-sftp')->exists(); - } - - /** - * Check if user has permission to reset the SFTP password for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function resetSftp(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('reset-sftp')->exists(); - } - - /** - * Check if user has permission to view the SFTP password for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function viewSftpPassword(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('view-sftp-password')->exists(); - } - - /** - * Check if user has permission to view databases for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function viewDatabases(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('view-databases')->exists(); - } - - /** - * Check if user has permission to reset database passwords. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function resetDbPassword(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('reset-db-password')->exists(); - } - - /** - * Check if user has permission to view all tasks for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function listTasks(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('list-tasks')->exists(); - } - - /** - * Check if user has permission to view a specific task for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function viewTask(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('view-task')->exists(); - } - - /** - * Check if user has permission to view a toggle a task for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function toggleTask(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('toggle-task')->exists(); - } - - /** - * Check if user has permission to queue a task for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function queueTask(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('queue-task')->exists(); - } - - /** - * Check if user has permission to delete a specific task for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function deleteTask(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('delete-task')->exists(); - } - - /** - * Check if user has permission to create a task for a server. - * - * @param Pterodactyl\Models\User $user - * @param Pterodactyl\Models\Server $server - * @return boolean - */ - public function createTask(User $user, Server $server) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission('create-task')->exists(); - } - } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d0d1dd9e4..03fafc49c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -1,7 +1,33 @@ . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ namespace Pterodactyl\Providers; +use View; +use Cache; +use Pterodactyl\Models; +use Pterodactyl\Observers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -13,7 +39,12 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { - // + Models\User::observe(Observers\UserObserver::class); + Models\Server::observe(Observers\ServerObserver::class); + Models\Subuser::observe(Observers\SubuserObserver::class); + + View::share('appVersion', $this->versionData()['version'] ?? 'undefined'); + View::share('appIsGit', $this->versionData()['is_git'] ?? false); } /** @@ -23,6 +54,39 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - // + if ($this->app->environment() !== 'production') { + $this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class); + } + + if (config('pterodactyl.auth.notifications')) { + $this->app->register(\DaneEveritt\LoginNotifications\NotificationServiceProvider::class); + } + } + + /** + * Return version information for the footer. + * + * @return array + */ + protected function versionData() + { + return Cache::remember('git-version', 5, function () { + if (file_exists(base_path('.git/HEAD'))) { + $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); + $path = base_path('.git/' . trim($head[1])); + } + + if (isset($path) && file_exists($path)) { + return [ + 'version' => substr(file_get_contents($path), 0, 8), + 'is_git' => true, + ]; + } + + return [ + 'version' => config('app.version'), + 'is_git' => false, + ]; + }); } } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 757101e2d..e1401e844 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Providers; -use Illuminate\Contracts\Auth\Access\Gate as GateContract; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider @@ -13,7 +12,8 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - 'Pterodactyl\Models\Server' => 'Pterodactyl\Policies\ServerPolicy' + 'Pterodactyl\Models\Server' => 'Pterodactyl\Policies\ServerPolicy', + 'Pterodactyl\Models\APIKey' => 'Pterodactyl\Policies\APIKeyPolicy', ]; /** @@ -22,10 +22,8 @@ class AuthServiceProvider extends ServiceProvider * @param \Illuminate\Contracts\Auth\Access\Gate $gate * @return void */ - public function boot(GateContract $gate) + public function boot() { - parent::registerPolicies($gate); - - // + $this->registerPolicies(); } } diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php index 1dcf8d287..e61e610d5 100644 --- a/app/Providers/BroadcastServiceProvider.php +++ b/app/Providers/BroadcastServiceProvider.php @@ -1,6 +1,6 @@ [ - 'Pterodactyl\Listeners\DeleteServerListener', - ], - ]; + protected $listen = []; /** * Register any other events for your application. @@ -26,7 +22,5 @@ class EventServiceProvider extends ServiceProvider public function boot() { parent::boot(); - - // } } diff --git a/app/Providers/MacroServiceProvider.php b/app/Providers/MacroServiceProvider.php new file mode 100644 index 000000000..8dd08f73b --- /dev/null +++ b/app/Providers/MacroServiceProvider.php @@ -0,0 +1,86 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Providers; + +use File; +use Cache; +use Carbon; +use Request; +use Pterodactyl\Models\APIKey; +use Illuminate\Support\ServiceProvider; + +class MacroServiceProvider extends ServiceProvider +{ + /** + * Bootstrap the application services. + * + * @return void + */ + public function boot() + { + File::macro('humanReadableSize', function ($path, $precision = 2) { + $size = File::size($path); + static $units = ['B', 'kB', 'MB', 'GB', 'TB']; + + $i = 0; + while (($size / 1024) > 0.9) { + $size = $size / 1024; + $i++; + } + + return round($size, ($i < 2) ? 0 : $precision) . ' ' . $units[$i]; + }); + + Request::macro('apiKey', function () { + if (! Request::bearerToken()) { + return false; + } + + $parts = explode('.', Request::bearerToken()); + + if (count($parts) === 2 && strlen($parts[0]) === APIKey::PUBLIC_KEY_LEN) { + // Because the key itself isn't changing frequently, we simply cache this for + // 15 minutes to speed up the API and keep requests flowing. + return Cache::tags([ + 'ApiKeyMacro', + 'ApiKeyMacro:Key:' . $parts[0], + ])->remember('ApiKeyMacro.' . $parts[0], Carbon::now()->addMinutes(15), function () use ($parts) { + return APIKey::where('public', $parts[0])->first(); + }); + } + + return false; + }); + + Request::macro('apiKeyHasPermission', function ($permission) { + $key = Request::apiKey(); + if (! $key) { + return false; + } + + return Request::user()->can($permission, $key); + }); + } +} diff --git a/app/Providers/PhraseAppTranslationProvider.php b/app/Providers/PhraseAppTranslationProvider.php new file mode 100644 index 000000000..840234917 --- /dev/null +++ b/app/Providers/PhraseAppTranslationProvider.php @@ -0,0 +1,61 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Providers; + +use Pterodactyl\Extensions\PhraseAppTranslator; +use Illuminate\Translation\TranslationServiceProvider; +use Illuminate\Translation\Translator as IlluminateTranslator; + +class PhraseAppTranslationProvider extends TranslationServiceProvider +{ + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerLoader(); + + $this->app->singleton('translator', function ($app) { + $loader = $app['translation.loader']; + + // When registering the translator component, we'll need to set the default + // locale as well as the fallback locale. So, we'll grab the application + // configuration so we can easily get both of these values from there. + $locale = $app['config']['app.locale']; + + if ($app['config']['pterodactyl.lang.in_context']) { + $trans = new PhraseAppTranslator($loader, $locale); + } else { + $trans = new IlluminateTranslator($loader, $locale); + } + + $trans->setFallback($app['config']['app.fallback_locale']); + + return $trans; + }); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f25c59471..00d40bdbd 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -19,13 +19,10 @@ class RouteServiceProvider extends ServiceProvider /** * Define your route model bindings, pattern filters, etc. * - * @param \Illuminate\Routing\Router $router * @return void */ public function boot() { - // - parent::boot(); } @@ -36,10 +33,32 @@ class RouteServiceProvider extends ServiceProvider */ public function map() { - Route::group(['namespace' => $this->namespace], function ($router) { - foreach (glob(app_path('Http//Routes') . '/*.php') as $file) { - $this->app->make('Pterodactyl\\Http\\Routes\\' . basename($file, '.php'))->map($router); - } - }); + Route::middleware(['api'])->prefix('/api/user') + ->namespace($this->namespace . '\API\User') + ->group(base_path('routes/api.php')); + + Route::middleware(['api'])->prefix('/api/admin') + ->namespace($this->namespace . '\API\Admin') + ->group(base_path('routes/api-admin.php')); + + Route::middleware(['web', 'auth', 'csrf']) + ->namespace($this->namespace . '\Base') + ->group(base_path('routes/base.php')); + + Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') + ->namespace($this->namespace . '\Admin') + ->group(base_path('routes/admin.php')); + + Route::middleware(['web', 'csrf'])->prefix('/auth') + ->namespace($this->namespace . '\Auth') + ->group(base_path('routes/auth.php')); + + Route::middleware(['web', 'auth', 'server', 'csrf'])->prefix('/server/{server}') + ->namespace($this->namespace . '\Server') + ->group(base_path('routes/server.php')); + + Route::middleware(['web', 'daemon'])->prefix('/daemon') + ->namespace($this->namespace . '\Daemon') + ->group(base_path('routes/daemon.php')); } } diff --git a/app/Repositories/APIRepository.php b/app/Repositories/APIRepository.php index c093c86ed..10af25155 100644 --- a/app/Repositories/APIRepository.php +++ b/app/Repositories/APIRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,113 +21,71 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; -use Auth; use DB; +use Auth; use Crypt; use Validator; use IPTools\Network; - -use Pterodactyl\Models; +use Pterodactyl\Models\User; +use Pterodactyl\Models\APIKey as Key; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Models\APIPermission as Permission; use Pterodactyl\Exceptions\DisplayValidationException; class APIRepository { - - /** - * Valid API permissions. - * @var array - */ - protected $permissions = [ - 'admin' => [ - '*', - - // User Management Routes - 'users.list', - 'users.create', - 'users.view', - 'users.update', - 'users.delete', - - // Server Manaement Routes - 'servers.list', - 'servers.create', - 'servers.view', - 'servers.config', - 'servers.build', - 'servers.suspend', - 'servers.unsuspend', - 'servers.delete', - - // Node Management Routes - 'nodes.list', - 'nodes.create', - 'nodes.list', - 'nodes.allocations', - 'nodes.delete', - - // Service Routes - 'services.list', - 'services.view', - - // Location Routes - 'locations.list', - - ], - 'user' => [ - '*', - - // Informational - 'me', - - // Server Control - 'server', - 'server.power', - ], - ]; - /** * Holder for listing of allowed IPs when creating a new key. + * * @var array */ protected $allowed = []; + /** + * The eloquent model for a user. + * + * @var \Pterodactyl\Models\User + */ protected $user; /** - * Constructor + * Constructor for API Repository. + * + * @param null|\Pterodactyl\Models\User $user + * @return void */ - public function __construct(Models\User $user = null) + public function __construct(User $user = null) { $this->user = is_null($user) ? Auth::user() : $user; if (is_null($this->user)) { - throw new \Exception('Cannot access API Repository without passing a user to __construct().'); + throw new \Exception('Unable to initialize user for API repository instance.'); } } /** * Create a New API Keypair on the system. * - * @param array $data An array with a permissions and allowed_ips key. + * @param array $data + * @return string * - * @throws Pterodactyl\Exceptions\DisplayException if there was an error that can be safely displayed to end-users. - * @throws Pterodactyl\Exceptions\DisplayValidationException if there was a validation error. - * - * @return string Returns the generated secret token. + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ - public function new(array $data) + public function create(array $data) { $validator = Validator::make($data, [ 'memo' => 'string|max:500', + 'allowed_ips' => 'sometimes|string', 'permissions' => 'sometimes|required|array', - 'adminPermissions' => 'sometimes|required|array' + 'admin_permissions' => 'sometimes|required|array', ]); - $validator->after(function($validator) use ($data) { - if (array_key_exists('allowed_ips', $data) && !empty($data['allowed_ips'])) { - foreach(explode("\n", $data['allowed_ips']) as $ip) { + $validator->after(function ($validator) use ($data) { + if (array_key_exists('allowed_ips', $data) && ! empty($data['allowed_ips'])) { + foreach (explode("\n", $data['allowed_ips']) as $ip) { $ip = trim($ip); try { Network::parse($ip); @@ -142,55 +100,75 @@ class APIRepository // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } DB::beginTransaction(); try { $secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7); - $key = new Models\APIKey; - $key->fill([ - 'user' => $this->user->id, + $key = Key::create([ + 'user_id' => $this->user->id, 'public' => str_random(16), 'secret' => Crypt::encrypt($secretKey), 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed), 'memo' => $data['memo'], - 'expires_at' => null + 'expires_at' => null, ]); - $key->save(); $totalPermissions = 0; - if (isset($data['permissions'])) { - foreach($data['permissions'] as $permNode) { - if (!strpos($permNode, ':')) continue; + $pNodes = Permission::permissions(); - list($toss, $permission) = explode(':', $permNode); - if (in_array($permission, $this->permissions['user'])) { - $totalPermissions++; - $model = new Models\APIPermission; - $model->fill([ - 'key_id' => $key->id, - 'permission' => 'api.user.' . $permission - ]); - $model->save(); + if (isset($data['permissions'])) { + foreach ($data['permissions'] as $permission) { + $parts = explode('-', $permission); + + if (count($parts) !== 2) { + continue; } + + list($block, $search) = $parts; + + if (! array_key_exists($block, $pNodes['_user'])) { + continue; + } + + if (! in_array($search, $pNodes['_user'][$block])) { + continue; + } + + $totalPermissions++; + Permission::create([ + 'key_id' => $key->id, + 'permission' => 'user.' . $permission, + ]); } } - if ($this->user->root_admin === 1 && isset($data['adminPermissions'])) { - foreach($data['adminPermissions'] as $permNode) { - if (!strpos($permNode, ':')) continue; + if ($this->user->isRootAdmin() && isset($data['admin_permissions'])) { + unset($pNodes['_user']); - list($toss, $permission) = explode(':', $permNode); - if (in_array($permission, $this->permissions['admin'])) { - $totalPermissions++; - $model = new Models\APIPermission; - $model->fill([ - 'key_id' => $key->id, - 'permission' => 'api.admin.' . $permission - ]); - $model->save(); + foreach ($data['admin_permissions'] as $permission) { + $parts = explode('-', $permission); + + if (count($parts) !== 2) { + continue; } + + list($block, $search) = $parts; + + if (! array_key_exists($block, $pNodes)) { + continue; + } + + if (! in_array($search, $pNodes[$block])) { + continue; + } + + $totalPermissions++; + Permission::create([ + 'key_id' => $key->id, + 'permission' => $permission, + ]); } } @@ -199,37 +177,31 @@ class APIRepository } DB::commit(); + return $secretKey; } catch (\Exception $ex) { DB::rollBack(); throw $ex; } - } /** * Revokes an API key and associated permissions. * - * @param string $key The public key. - * - * @throws Illuminate\Database\Eloquent\ModelNotFoundException - * + * @param string $key * @return void + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - public function revoke(string $key) + public function revoke($key) { - DB::beginTransaction(); + DB::transaction(function () use ($key) { + $model = Key::with('permissions')->where('public', $key)->where('user_id', $this->user->id)->firstOrFail(); + foreach ($model->permissions as &$permission) { + $permission->delete(); + } - try { - $model = Models\APIKey::where('public', $key)->where('user', $this->user->id)->firstOrFail(); - $permissions = Models\APIPermission::where('key_id', $model->id)->delete(); $model->delete(); - - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } + }); } - } diff --git a/app/Repositories/Daemon/CommandRepository.php b/app/Repositories/Daemon/CommandRepository.php index ebaf9dc61..beb9e8530 100644 --- a/app/Repositories/Daemon/CommandRepository.php +++ b/app/Repositories/Daemon/CommandRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,59 +21,71 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories\Daemon; -use Pterodactyl\Models; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\ConnectException; use Pterodactyl\Exceptions\DisplayException; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; - -class CommandRepository { - +class CommandRepository +{ + /** + * The Eloquent Model associated with the requested server. + * + * @var \Pterodactyl\Models\Server + */ protected $server; - protected $node; - protected $client; - public function __construct($server) + /** + * The Eloquent Model associated with the user to run the request as. + * + * @var \Pterodactyl\Models\User|null + */ + protected $user; + + /** + * Constuctor for repository. + * + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\User|null $user + * @return void + */ + public function __construct(Server $server, User $user = null) { - $this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server); - $this->node = Models\Node::getByID($this->server->node); - $this->client = Models\Node::guzzleRequest($this->server->node); + $this->server = $server; + $this->user = $user; } /** - * [send description] - * @param string $command - * @return boolean - * @throws DisplayException - * @throws RequestException + * Sends a command to the daemon. + * + * @param string $command + * @return string + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \GuzzleHttp\Exception\RequestException */ public function send($command) { // We don't use the user's specific daemon secret here since we // are assuming that a call to this function has been validated. - // Additionally not all calls to this will be from a logged in user. - // (e.g. task queue or API) try { - $response = $this->client->request('POST', '/server/command', [ - 'headers' => [ - 'X-Access-Token' => $this->server->daemonSecret, - 'X-Access-Server' => $this->server->uuid - ], + $response = $this->server->guzzleClient($this->user)->request('POST', '/server/command', [ + 'http_errors' => false, 'json' => [ - 'command' => $command - ] + 'command' => $command, + ], ]); if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { - throw new DisplayException('Command sending responded with a non-200 error code.'); + throw new DisplayException('Command sending responded with a non-200 error code (HTTP/' . $response->getStatusCode() . ').'); } return $response->getBody(); - } catch (\Exception $ex) { + } catch (ConnectException $ex) { throw $ex; } } - } diff --git a/app/Repositories/Daemon/FileRepository.php b/app/Repositories/Daemon/FileRepository.php index 1d6bf7aac..e789f7469 100644 --- a/app/Repositories/Daemon/FileRepository.php +++ b/app/Repositories/Daemon/FileRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,93 +21,61 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories\Daemon; -use \Exception; -use Log; - -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Node; -use Pterodactyl\Repositories\HelperRepository; -use Pterodactyl\Exceptions\DisplayException; - +use Exception; use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Models\Server; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Repositories\HelperRepository; class FileRepository { - /** * The Eloquent Model associated with the requested server. * - * @var \Illuminate\Database\Eloquent\Model + * @var \Pterodactyl\Models\Server */ protected $server; /** - * The Eloquent Model for the node corresponding with the requested server. + * Constructor. * - * @var \Illuminate\Database\Eloquent\Model - */ - protected $node; - - /** - * The Guzzle Client associated with the requested server and node. - * - * @var \GuzzleHttp\Client - */ - protected $client; - - /** - * The Guzzle Client headers associated with the requested server and node. - * (non-administrative headers) - * - * @var array - */ - protected $headers; - - /** - * Constructor - * - * @param string $server The server Short UUID + * @param string $uuid + * @return void */ public function __construct($uuid) { - - $this->server = Server::getByUUID($uuid); - $this->node = Node::getByID($this->server->node); - $this->client = Node::guzzleRequest($this->server->node); - $this->headers = Server::getGuzzleHeaders($uuid); - + $this->server = Server::byUuid($uuid); } /** * Get the contents of a requested file for the server. * - * @param string $file + * @param string $file * @return array + * + * @throws \GuzzleHttp\Exception\RequestException + * @throws \Pterodactyl\Exceptions\DisplayException */ public function returnFileContents($file) { - if (empty($file)) { throw new Exception('Not all parameters were properly passed to the function.'); } $file = (object) pathinfo($file); - $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; - $res = $this->client->request('GET', '/server/file/stat/' . rawurlencode($file->dirname.$file->basename) , [ - 'headers' => $this->headers - ]); + $res = $this->server->guzzleClient()->request('GET', '/server/file/stat/' . rawurlencode($file->dirname . $file->basename)); $stat = json_decode($res->getBody()); - if($res->getStatusCode() !== 200 || !isset($stat->size)) { + if ($res->getStatusCode() !== 200 || ! isset($stat->size)) { throw new DisplayException('The daemon provided a non-200 error code on stat lookup: HTTP\\' . $res->getStatusCode()); } - if (!in_array($stat->mime, HelperRepository::editableFiles())) { + if (! in_array($stat->mime, HelperRepository::editableFiles())) { throw new DisplayException('You cannot edit that type of file (' . $stat->mime . ') through the panel.'); } @@ -115,46 +83,43 @@ class FileRepository throw new DisplayException('That file is too large to open in the browser, consider using a SFTP client.'); } - $res = $this->client->request('GET', '/server/file/f/' . rawurlencode($file->dirname.$file->basename) , [ - 'headers' => $this->headers - ]); + $res = $this->server->guzzleClient()->request('GET', '/server/file/f/' . rawurlencode($file->dirname . $file->basename)); $json = json_decode($res->getBody()); - if($res->getStatusCode() !== 200 || !isset($json->content)) { + if ($res->getStatusCode() !== 200 || ! isset($json->content)) { throw new DisplayException('The daemon provided a non-200 error code: HTTP\\' . $res->getStatusCode()); } return [ 'file' => $json, - 'stat' => $stat + 'stat' => $stat, ]; - } /** - * Save the contents of a requested file on the Scales instance. + * Save the contents of a requested file on the daemon. * - * @param string $file - * @param string $content + * @param string $file + * @param string $content * @return bool + * + * @throws \GuzzleHttp\Exception\RequestException + * @throws \Pterodactyl\Exceptions\DisplayException */ public function saveFileContents($file, $content) { - if (empty($file)) { throw new Exception('A valid file and path must be specified to save a file.'); } $file = (object) pathinfo($file); - $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; - $res = $this->client->request('POST', '/server/file/save', [ - 'headers' => $this->headers, + $res = $this->server->guzzleClient()->request('POST', '/server/file/save', [ 'json' => [ - 'path' => rawurlencode($file->dirname.$file->basename), - 'content' => $content - ] + 'path' => rawurlencode($file->dirname . $file->basename), + 'content' => $content, + ], ]); if ($res->getStatusCode() !== 204) { @@ -162,67 +127,67 @@ class FileRepository } return true; - } /** - * Returns a listing of all files and folders within a specified Scales directory. + * Returns a listing of all files and folders within a specified directory on the daemon. * - * @param string $directory + * @param string $directory * @return object + * + * @throws \GuzzleHttp\Exception\RequestException + * @throws \Pterodactyl\Exceptions\DisplayException */ public function returnDirectoryListing($directory) { - if (empty($directory)) { throw new Exception('A valid directory must be specified in order to list its contents.'); } - $res = $this->client->request('GET', '/server/directory/' . rawurlencode($directory), [ - 'headers' => $this->headers - ]); + try { + $res = $this->server->guzzleClient()->request('GET', '/server/directory/' . rawurlencode($directory)); + } catch (\GuzzleHttp\Exception\ClientException $ex) { + $json = json_decode($ex->getResponse()->getBody()); + + throw new DisplayException($json->error); + } catch (\GuzzleHttp\Exception\ServerException $ex) { + throw new DisplayException('A remote server error was encountered while attempting to display this directory.'); + } catch (\GuzzleHttp\Exception\ConnectException $ex) { + throw new DisplayException('A ConnectException was encountered: unable to contact daemon.'); + } catch (\Exception $ex) { + throw $ex; + } $json = json_decode($res->getBody()); - if($res->getStatusCode() !== 200) { - throw new DisplayException('An error occured while attempting to save this file. ' . $res->getBody()); - } // Iterate through results $files = []; $folders = []; - foreach($json as &$value) { - - if ($value->directory === true) { - + foreach ($json as &$value) { + if ($value->directory) { // @TODO Handle Symlinks - $folders = array_merge($folders, [[ + $folders[] = [ 'entry' => $value->name, 'directory' => trim($directory, '/'), 'size' => null, 'date' => strtotime($value->modified), - 'mime' => $value->mime - ]]); - - } else if ($value->file === true) { - - $files = array_merge($files, [[ + 'mime' => $value->mime, + ]; + } elseif ($value->file) { + $files[] = [ 'entry' => $value->name, 'directory' => trim($directory, '/'), 'extension' => pathinfo($value->name, PATHINFO_EXTENSION), 'size' => HelperRepository::bytesToHuman($value->size), 'date' => strtotime($value->modified), - 'mime' => $value->mime - ]]); - + 'mime' => $value->mime, + ]; } - } return (object) [ 'files' => $files, 'folders' => $folders, ]; - } - } diff --git a/app/Repositories/Daemon/PowerRepository.php b/app/Repositories/Daemon/PowerRepository.php index 2fa56d5da..925379096 100644 --- a/app/Repositories/Daemon/PowerRepository.php +++ b/app/Repositories/Daemon/PowerRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,72 +21,109 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories\Daemon; -use Pterodactyl\Models; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\ConnectException; use Pterodactyl\Exceptions\DisplayException; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; - -class PowerRepository { - +class PowerRepository +{ + /** + * The Eloquent Model associated with the requested server. + * + * @var \Pterodactyl\Models\Server + */ protected $server; - protected $node; - protected $client; - public function __construct($server) + /** + * The Eloquent Model associated with the user to run the request as. + * + * @var \Pterodactyl\Models\User|null + */ + protected $user; + + /** + * Constuctor for repository. + * + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\User|null $user + * @return void + */ + public function __construct(Server $server, User $user = null) { - $this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server); - $this->node = Models\Node::getByID($this->server->node); - $this->client = Models\Node::guzzleRequest($this->server->node); + $this->server = $server; + $this->user = $user; } + /** + * Sends a power option to the daemon. + * + * @param string $action + * @return string + * + * @throws \GuzzleHttp\Exception\RequestException + * @throws \Pterodactyl\Exceptions\DisplayException + */ public function do($action) { - // We don't use the user's specific daemon secret here since we - // are assuming that a call to this function has been validated. - // Additionally not all calls to this will be from a logged in user. - // (e.g. task queue or API) try { - $response = $this->client->request('PUT', '/server/power', [ - 'headers' => [ - 'X-Access-Token' => $this->server->daemonSecret, - 'X-Access-Server' => $this->server->uuid - ], + $response = $this->server->guzzleClient($this->user)->request('PUT', '/server/power', [ + 'http_errors' => false, 'json' => [ - 'action' => $action - ] + 'action' => $action, + ], ]); if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { - throw new DisplayException('Power status responded with a non-200 error code.'); + throw new DisplayException('Power toggle endpoint responded with a non-200 error code (HTTP/' . $response->getStatusCode() . ').'); } return $response->getBody(); - } catch (\Exception $ex) { + } catch (ConnectException $ex) { throw $ex; } } + /** + * Starts a server. + * + * @return void + */ public function start() { $this->do('start'); } + /** + * Stops a server. + * + * @return void + */ public function stop() { $this->do('stop'); } + /** + * Restarts a server. + * + * @return void + */ public function restart() { $this->do('restart'); } + /** + * Kills a server. + * + * @return void + */ public function kill() { $this->do('kill'); } - } diff --git a/app/Repositories/DatabaseRepository.php b/app/Repositories/DatabaseRepository.php index c1c9b4e37..f7f428a6f 100644 --- a/app/Repositories/DatabaseRepository.php +++ b/app/Repositories/DatabaseRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,258 +21,264 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; -use Crypt; -use Log; use DB; +use Crypt; use Validator; - -use Pterodactyl\Models; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Database; +use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayValidationException; -use Illuminate\Database\Capsule\Manager as Capsule; - -class DatabaseRepository { - +class DatabaseRepository +{ /** - * Adds a new database to a given database server. - * @param int $server Id of the server to add a database for. - * @param array $options Array of options for creating that database. - * @return void + * Adds a new database to a specified database host server. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Database + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ - public function create($server, $options) + public function create($id, array $data) { - $server = Models\Server::findOrFail($server); - $validator = Validator::make($options, [ - 'db_server' => 'required|exists:database_servers,id', + $server = Server::findOrFail($id); + + $validator = Validator::make($data, [ + 'host' => 'required|exists:database_hosts,id', 'database' => 'required|regex:/^\w{1,100}$/', - 'remote' => 'required|regex:/^[0-9%.]{1,15}$/', + 'connection' => 'required|regex:/^[0-9%.]{1,15}$/', ]); if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } + $host = DatabaseHost::findOrFail($data['host']); DB::beginTransaction(); try { - $db = new Models\Database; - $db->fill([ + $database = Database::firstOrNew([ 'server_id' => $server->id, - 'db_server' => $options['db_server'], - 'database' => $server->uuidShort . '_' . $options['database'], - 'username' => $server->uuidShort . '_' . str_random(7), - 'remote' => $options['remote'], - 'password' => Crypt::encrypt(str_random(20)) - ]); - $db->save(); - - // Contact Remote - $dbr = Models\DatabaseServer::findOrFail($options['db_server']); - - $capsule = new Capsule; - $capsule->addConnection([ - 'driver' => 'mysql', - 'host' => $dbr->host, - 'port' => $dbr->port, - 'database' => 'mysql', - 'username' => $dbr->username, - 'password' => Crypt::decrypt($dbr->password), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'options' => [ - \PDO::ATTR_TIMEOUT => 3, - ] + 'database_host_id' => $data['host'], + 'database' => sprintf('s%d_%s', $server->id, $data['database']), ]); - $capsule->setAsGlobal(); + if ($database->exists) { + throw new DisplayException('A database with those details already exists in the system.'); + } - Capsule::statement('CREATE DATABASE ' . $db->database); - Capsule::statement('CREATE USER \'' . $db->username . '\'@\'' . $db->remote . '\' IDENTIFIED BY \'' . Crypt::decrypt($db->password) . '\''); - Capsule::statement('GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON ' . $db->database . '.* TO \'' . $db->username . '\'@\'' . $db->remote . '\''); - Capsule::statement('FLUSH PRIVILEGES'); + $database->username = sprintf('s%d_%s', $server->id, str_random(10)); + $database->remote = $data['connection']; + $database->password = Crypt::encrypt(str_random(20)); - DB::commit(); - return true; + $database->save(); } catch (\Exception $ex) { - DB::rollback(); + DB::rollBack(); + throw $ex; + } + + try { + $host->setDynamicConnection(); + + DB::connection('dynamic')->statement(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database->database)); + DB::connection('dynamic')->statement(sprintf( + 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', + $database->username, $database->remote, Crypt::decrypt($database->password) + )); + DB::connection('dynamic')->statement(sprintf( + 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', + $database->database, $database->username, $database->remote + )); + + DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); + + // Save Everything + DB::commit(); + + return $database; + } catch (\Exception $ex) { + try { + DB::connection('dynamic')->statement(sprintf('DROP DATABASE IF EXISTS `%s`', $database->database)); + DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); + DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); + } catch (\Exception $ex) { + } + + DB::rollBack(); throw $ex; } } /** * Updates the password for a given database. - * @param int $database The ID of the database to modify. - * @param string $password The new password to use for the database. - * @return bool + * + * @param int $id + * @param string $password + * @return void + * + * @todo Fix logic behind resetting passwords. */ - public function modifyPassword($database, $password) + public function password($id, $password) { - $db = Models\Database::findOrFail($database); - $dbr = Models\DatabaseServer::findOrFail($db->db_server); + $database = Database::with('host')->findOrFail($id); + $database->host->setDynamicConnection(); - DB::beginTransaction(); - try { + DB::transaction(function () use ($database, $password) { + $database->password = Crypt::encrypt($password); - $db->password = Crypt::encrypt($password); - $db->save(); - - $capsule = new Capsule; - $capsule->addConnection([ - 'driver' => 'mysql', - 'host' => $dbr->host, - 'port' => $dbr->port, - 'database' => 'mysql', - 'username' => $dbr->username, - 'password' => Crypt::decrypt($dbr->password), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'options' => [ - \PDO::ATTR_TIMEOUT => 3, - ] - ]); - - $capsule->setAsGlobal(); - Capsule::statement(sprintf( - 'SET PASSWORD FOR \'%s\'@\'%s\' = PASSWORD(\'%s\')', - $db->username, - $db->remote, - $password + // We have to do the whole delete user, create user thing rather than + // SET PASSWORD ... because MariaDB and PHP statements ends up inserting + // a corrupted password. A way around this is strtoupper(sha1(sha1($password, true))) + // but no garuntees that will work correctly with every system. + DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); + DB::connection('dynamic')->statement(sprintf( + 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', + $database->username, $database->remote, $password )); + DB::connection('dynamic')->statement(sprintf( + 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', + $database->database, $database->username, $database->remote + )); + DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - DB::commit(); - } catch(\Exception $ex) { - DB::rollback(); - throw $ex; - } + $database->save(); + }); } /** - * Drops a database from the associated MySQL Server - * @param int $database The ID of the database to drop. - * @return boolean + * Drops a database from the associated database host. + * + * @param int $id + * @return void */ - public function drop($database) + public function drop($id) { - $db = Models\Database::findOrFail($database); - $dbr = Models\DatabaseServer::findOrFail($db->db_server); + $database = Database::with('host')->findOrFail($id); + $database->host->setDynamicConnection(); - DB::beginTransaction(); - - try { - $capsule = new Capsule; - $capsule->addConnection([ - 'driver' => 'mysql', - 'host' => $dbr->host, - 'port' => $dbr->port, - 'database' => 'mysql', - 'username' => $dbr->username, - 'password' => Crypt::decrypt($dbr->password), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'options' => [ - \PDO::ATTR_TIMEOUT => 3, - ] - ]); - - $capsule->setAsGlobal(); - - Capsule::statement('DROP USER \'' . $db->username . '\'@\'' . $db->remote . '\''); - Capsule::statement('DROP DATABASE ' . $db->database); - - $db->delete(); - - DB::commit(); - return true; - } catch (\Exception $ex) { - DB::rollback(); - throw $ex; - } + DB::transaction(function () use ($database) { + DB::connection('dynamic')->statement(sprintf('DROP DATABASE IF EXISTS `%s`', $database->database)); + DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); + DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); + $database->delete(); + }); } /** - * Deletes a database server from the system if it is empty. - * @param int $server The ID of the Database Server. - * @return + * Deletes a database host from the system if it has no associated databases. + * + * @param int $id + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete($server) + public function delete($id) { - $dbh = Models\DatabaseServer::findOrFail($server); - $databases = Models\Database::where('db_server', $dbh->id)->count(); + $host = DatabaseHost::withCount('databases')->findOrFail($id); - if ($databases > 0) { - throw new DisplayException('You cannot delete a database server that has active databases attached to it.'); + if ($host->databases_count > 0) { + throw new DisplayException('You cannot delete a database host that has active databases attached to it.'); } - return $dbh->delete(); + $host->delete(); } /** - * Adds a new Database Server to the system. - * @param array $data + * Adds a new Database Host to the system. + * + * @param array $data + * @return \Pterodactyl\Models\DatabaseHost + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function add(array $data) { + if (isset($data['host'])) { + $data['host'] = gethostbyname($data['host']); + } + $validator = Validator::make($data, [ 'name' => 'required|string|max:255', - 'host' => 'required|ip|unique:database_servers,host', + 'host' => 'required|ip|unique:database_hosts,host', 'port' => 'required|numeric|between:1,65535', 'username' => 'required|string|max:32', 'password' => 'required|string', - 'linked_node' => 'sometimes', + 'node_id' => 'sometimes|required|exists:nodes,id', ]); if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } - DB::beginTransaction(); + return DB::transaction(function () use ($data) { + $host = new DatabaseHost; + $host->password = Crypt::encrypt($data['password']); - try { - $capsule = new Capsule; - $capsule->addConnection([ - 'driver' => 'mysql', - 'host' => $data['host'], - 'port' => $data['port'], - 'database' => 'mysql', - 'username' => $data['username'], - 'password' => $data['password'], - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'options' => [ - \PDO::ATTR_TIMEOUT => 3, - ] - ]); - - $capsule->setAsGlobal(); - - // Allows us to check that we can connect to things. - Capsule::select('SELECT 1 FROM dual'); - - $dbh = new Models\DatabaseServer; - $dbh->fill([ + $host->fill([ 'name' => $data['name'], 'host' => $data['host'], 'port' => $data['port'], 'username' => $data['username'], - 'password' => Crypt::encrypt($data['password']), - 'max_databases' => NULL, - 'linked_node' => (!empty($data['linked_node']) && $data['linked_node'] > 0) ? $data['linked_node'] : NULL - ]); - $dbh->save(); + 'max_databases' => null, + 'node_id' => (isset($data['node_id'])) ? $data['node_id'] : null, + ])->save(); - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } + // Allows us to check that we can connect to things. + $host->setDynamicConnection(); + DB::connection('dynamic')->select('SELECT 1 FROM dual'); + + return $host; + }); } + /** + * Updates a Database Host on the system. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\DatabaseHost + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function update($id, array $data) + { + $host = DatabaseHost::findOrFail($id); + + if (isset($data['host'])) { + $data['host'] = gethostbyname($data['host']); + } + + $validator = Validator::make($data, [ + 'name' => 'sometimes|required|string|max:255', + 'host' => 'sometimes|required|ip|unique:database_hosts,host,' . $host->id, + 'port' => 'sometimes|required|numeric|between:1,65535', + 'username' => 'sometimes|required|string|max:32', + 'password' => 'sometimes|required|string', + 'node_id' => 'sometimes|required|exists:nodes,id', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + return DB::transaction(function () use ($data, $host) { + if (isset($data['password'])) { + $host->password = Crypt::encrypt($data['password']); + } + $host->fill($data)->save(); + + // Check that we can still connect with these details. + $host->setDynamicConnection(); + DB::connection('dynamic')->select('SELECT 1 FROM dual'); + + return $host; + }); + } } diff --git a/app/Repositories/HelperRepository.php b/app/Repositories/HelperRepository.php index 6eaa68329..204480ec2 100644 --- a/app/Repositories/HelperRepository.php +++ b/app/Repositories/HelperRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,12 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; -class HelperRepository { - +class HelperRepository +{ /** * Listing of editable files in the control panel. + * * @var array */ protected static $editable = [ @@ -40,35 +42,32 @@ class HelperRepository { 'text/plain', 'text/x-perl', 'text/x-shellscript', - 'inode/x-empty' + 'inode/x-empty', ]; - - public function __construct() - { - // - } - /** * Converts from bytes to the largest possible size that is still readable. * - * @param int $bytes - * @param int $decimals + * @param int $bytes + * @param int $decimals * @return string + * @deprecated */ public static function bytesToHuman($bytes, $decimals = 2) { - $sz = explode(',', 'B,KB,MB,GB'); $factor = floor((strlen($bytes) - 1) / 3); - return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)).' '.$sz[$factor]; - + return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . $sz[$factor]; } + /** + * Returns array of editable files. + * + * @return array + */ public static function editableFiles() { return self::$editable; } - } diff --git a/app/Repositories/LocationRepository.php b/app/Repositories/LocationRepository.php index e70cfbd3e..5f08cfc17 100644 --- a/app/Repositories/LocationRepository.php +++ b/app/Repositories/LocationRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,80 +21,84 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; use Validator; - -use Pterodactyl\Models; +use Pterodactyl\Models\Location; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayValidationException; class LocationRepository { - - public function __construct() - { - // - } - /** * Creates a new location on the system. + * * @param array $data - * @throws Pterodactyl\Exceptions\DisplayValidationException - * @return integer + * @return \Pterodactyl\Models\Location + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function create(array $data) { $validator = Validator::make($data, [ - 'short' => 'required|regex:/^[a-z0-9_.-]{1,10}$/i|unique:locations,short', - 'long' => 'required|string|min:1|max:255' + 'short' => 'required|string|between:1,60|unique:locations,short', + 'long' => 'required|string|between:1,255', ]); - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } - $location = new Models\Location; - $location->fill([ + return Location::create([ 'long' => $data['long'], - 'short' => $data['short'] + 'short' => $data['short'], ]); - $location->save(); - - return $location->id; } /** - * Modifies a location based on the fields passed in $data. - * @param integer $id - * @param array $data - * @throws Pterodactyl\Exceptions\DisplayValidationException - * @return boolean + * Modifies a location. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Location + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ - public function edit($id, array $data) + public function update($id, array $data) { + $location = Location::findOrFail($id); + $validator = Validator::make($data, [ - 'short' => 'regex:/^[a-z0-9_.-]{1,10}$/i', - 'long' => 'string|min:1|max:255' + 'short' => 'sometimes|required|string|between:1,60|unique:locations,short,' . $location->id, + 'long' => 'sometimes|required|string|between:1,255', ]); - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } - $location = Models\Location::findOrFail($id); + $location->fill($data)->save(); - if (isset($data['short'])) { - $location->short = $data['short']; + return $location; + } + + /** + * Deletes a location from the system. + * + * @param int $id + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id) + { + $location = Location::withCount('nodes')->findOrFail($id); + + if ($location->nodes_count > 0) { + throw new DisplayException('Cannot delete a location that has nodes assigned to it.'); } - if (isset($data['long'])) { - $location->long = $data['long']; - } - - return $location->save(); + $location->delete(); } } diff --git a/app/Repositories/NodeRepository.php b/app/Repositories/NodeRepository.php index a23cc7455..2d6fd3c9c 100644 --- a/app/Repositories/NodeRepository.php +++ b/app/Repositories/NodeRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,34 +21,38 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; use DB; use Validator; - +use IPTools\Network; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; - -use IPTools\Network; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayValidationException; -class NodeRepository { - - public function __construct() - { - // - } - +class NodeRepository +{ + /** + * Creates a new node on the system. + * + * @param array $data + * @return \Pterodactyl\Models\Node + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ public function create(array $data) { // Validate Fields $validator = Validator::make($data, [ 'name' => 'required|regex:/^([\w .-]{1,100})$/', - 'location' => 'required|numeric|min:1|exists:locations,id', + 'location_id' => 'required|numeric|min:1|exists:locations,id', 'public' => 'required|numeric|between:0,1', 'fqdn' => 'required|string|unique:nodes,fqdn', 'scheme' => 'required|regex:/^(http(s)?)$/', + 'behind_proxy' => 'required|boolean', 'memory' => 'required|numeric|min:1', 'memory_overallocate' => 'required|numeric|min:-1', 'disk' => 'required|numeric|min:1', @@ -61,16 +65,16 @@ class NodeRepository { // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } // Verify the FQDN if using SSL if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') { - throw new DisplayException('A fully qualified domain name is required to use secure comunication on this node.'); + throw new DisplayException('A fully qualified domain name is required to use a secure comunication method on this node.'); } // Verify FQDN is resolvable, or if not using SSL that the IP is valid. - if (!filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { + if (! filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { throw new DisplayException('The FQDN (or IP Address) provided does not resolve to a valid IP address.'); } @@ -82,15 +86,19 @@ class NodeRepository { $uuid = new UuidService; $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); - // Store the Data - $node = new Models\Node; - $node->fill($data); - $node->save(); - - return $node->id; - + return Models\Node::create($data); } + /** + * Updates a node on the system. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Node + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ public function update($id, array $data) { $node = Models\Node::findOrFail($id); @@ -98,41 +106,42 @@ class NodeRepository { // Validate Fields $validator = $validator = Validator::make($data, [ 'name' => 'regex:/^([\w .-]{1,100})$/', - 'location' => 'numeric|min:1|exists:locations,id', + 'location_id' => 'numeric|min:1|exists:locations,id', 'public' => 'numeric|between:0,1', 'fqdn' => 'string|unique:nodes,fqdn,' . $id, 'scheme' => 'regex:/^(http(s)?)$/', + 'behind_proxy' => 'boolean', 'memory' => 'numeric|min:1', 'memory_overallocate' => 'numeric|min:-1', 'disk' => 'numeric|min:1', 'disk_overallocate' => 'numeric|min:-1', - 'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/', + 'upload_size' => 'numeric|min:0', + 'daemonBase' => 'sometimes|regex:/^([\/][\d\w.\-\/]+)$/', 'daemonSFTP' => 'numeric|between:1,65535', 'daemonListen' => 'numeric|between:1,65535', - 'reset_secret' => 'sometimes|accepted', + 'reset_secret' => 'sometimes|nullable|accepted', ]); // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } // Verify the FQDN if (isset($data['fqdn'])) { // Verify the FQDN if using SSL - if ((isset($data['scheme']) && $data['scheme'] === 'https') || (!isset($data['scheme']) && $node->scheme === 'https')) { + if ((isset($data['scheme']) && $data['scheme'] === 'https') || (! isset($data['scheme']) && $node->scheme === 'https')) { if (filter_var($data['fqdn'], FILTER_VALIDATE_IP)) { throw new DisplayException('A fully qualified domain name is required to use secure comunication on this node.'); } } // Verify FQDN is resolvable, or if not using SSL that the IP is valid. - if (!filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { + if (! filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { throw new DisplayException('The FQDN (or IP Address) provided does not resolve to a valid IP address.'); } - } // Should we be nulling the overallocations? @@ -145,92 +154,138 @@ class NodeRepository { } // Set the Secret - if (isset($data['reset_secret'])) { + if (isset($data['reset_secret']) && ! is_null($data['reset_secret'])) { $uuid = new UuidService; $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); unset($data['reset_secret']); } - // Store the Data - return $node->update($data); - - } - - public function addAllocations($id, array $allocations) - { - $node = Models\Node::findOrFail($id); - - DB::beginTransaction(); - + $oldDaemonKey = $node->daemonSecret; + $node->update($data); try { - foreach($allocations as $rawIP => $ports) { - try { - $setAlias = null; - $parsedIP = Network::parse($rawIP); - } catch (\Exception $ex) { - try { - $setAlias = $rawIP; - $parsedIP = Network::parse(gethostbyname($rawIP)); - } catch (\Exception $ex) { - throw $ex; - } - } - foreach($parsedIP as $ip) { - foreach($ports as $port) { - if (!is_int($port) && !preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) { - throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.'); - } - if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) { - foreach(range($matches[1], $matches[2]) as $assignPort) { - $alloc = Models\Allocation::firstOrNew([ - 'node' => $node->id, - 'ip' => $ip, - 'port' => $assignPort - ]); - if (!$alloc->exists) { - $alloc->fill([ - 'node' => $node->id, - 'ip' => $ip, - 'port' => $assignPort, - 'ip_alias' => $setAlias, - 'assigned_to' => null - ]); - $alloc->save(); - } - } - } else { - $alloc = Models\Allocation::firstOrNew([ - 'node' => $node->id, - 'ip' => $ip, - 'port' => $port - ]); - if (!$alloc->exists) { - $alloc->fill([ - 'node' => $node->id, - 'ip' => $ip, - 'port' => $port, - 'ip_alias' => $setAlias, - 'assigned_to' => null - ]); - $alloc->save(); - } - } - } - } - } - - DB::commit(); - // return true; + $node->guzzleClient(['X-Access-Token' => $oldDaemonKey])->request('PATCH', '/config', [ + 'json' => [ + 'web' => [ + 'listen' => $node->daemonListen, + 'ssl' => [ + 'enabled' => (! $node->behind_proxy && $node->scheme === 'https'), + ], + ], + 'sftp' => [ + 'path' => $node->daemonBase, + 'port' => $node->daemonSFTP, + ], + 'remote' => [ + 'base' => config('app.url'), + ], + 'uploads' => [ + 'size_limit' => $node->upload_size, + ], + 'keys' => [ + $node->daemonSecret, + ], + ], + ]); } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; + throw new DisplayException('Failed to update the node configuration, however your changes have been saved to the database. You will need to manually update the configuration file for the node to apply these changes.'); } } - public function delete($id) + /** + * Adds allocations to a provided node. + * + * @param int $id + * @param array $data + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function addAllocations($id, array $data) { - // @TODO: add logic; - return true; + $node = Models\Node::findOrFail($id); + + $validator = Validator::make($data, [ + 'allocation_ip' => 'required|string', + 'allocation_alias' => 'sometimes|required|string|max:255', + 'allocation_ports' => 'required|array', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + $explode = explode('/', $data['allocation_ip']); + if (count($explode) !== 1) { + if (! ctype_digit($explode[1]) || ($explode[1] > 32 || $explode[1] < 25)) { + throw new DisplayException('CIDR notation only allows masks between /32 and /25.'); + } + } + + DB::transaction(function () use ($data, $node) { + foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) { + foreach ($data['allocation_ports'] as $port) { + // Determine if this is a valid single port, or a valid port range. + if (! ctype_digit($port) && ! preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) { + throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.'); + } + + if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) { + $block = range($matches[1], $matches[2]); + + if (count($block) > 1000) { + throw new DisplayException('Adding more than 1000 ports at once is not supported. Please use a smaller port range.'); + } + + foreach ($block as $unit) { + // Insert into Database + Models\Allocation::firstOrCreate([ + 'node_id' => $node->id, + 'ip' => $ip, + 'port' => $unit, + 'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null, + 'server_id' => null, + ]); + } + } else { + // Insert into Database + Models\Allocation::firstOrCreate([ + 'node_id' => $node->id, + 'ip' => $ip, + 'port' => $port, + 'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null, + 'server_id' => null, + ]); + } + } + } + }); } + /** + * Deletes a node on the system. + * + * @param int $id + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id) + { + $node = Models\Node::withCount('servers')->findOrFail($id); + if ($node->servers_count > 0) { + throw new DisplayException('You cannot delete a node with servers currently attached to it.'); + } + + DB::transaction(function () use ($node) { + // Unlink Database Servers + Models\DatabaseHost::where('node_id', $node->id)->update(['node_id' => null]); + + // Delete Allocations + Models\Allocation::where('node_id', $node->id)->delete(); + + // Delete Node + $node->delete(); + }); + } } diff --git a/app/Repositories/OptionRepository.php b/app/Repositories/OptionRepository.php new file mode 100644 index 000000000..1a0ce4509 --- /dev/null +++ b/app/Repositories/OptionRepository.php @@ -0,0 +1,203 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories; + +use DB; +use Validator; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +class OptionRepository +{ + /** + * Creates a new service option on the system. + * + * @param array $data + * @return \Pterodactyl\Models\ServiceOption + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function create(array $data) + { + $validator = Validator::make($data, [ + 'service_id' => 'required|numeric|exists:services,id', + 'name' => 'required|string|max:255', + 'description' => 'required|string', + 'tag' => 'required|alpha_num|max:60|unique:service_options,tag', + 'docker_image' => 'sometimes|string|max:255', + 'startup' => 'sometimes|nullable|string', + 'config_from' => 'sometimes|required|numeric|exists:service_options,id', + 'config_startup' => 'required_without:config_from|json', + 'config_stop' => 'required_without:config_from|string|max:255', + 'config_logs' => 'required_without:config_from|json', + 'config_files' => 'required_without:config_from|json', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + if (isset($data['config_from'])) { + if (! ServiceOption::where('service_id', $data['service_id'])->where('id', $data['config_from'])->first()) { + throw new DisplayException('The `configuration from` directive must be a child of the assigned service.'); + } + } + + return ServiceOption::create($data); + } + + /** + * Deletes a service option from the system. + * + * @param int $id + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id) + { + $option = ServiceOption::with('variables')->withCount('servers')->findOrFail($id); + + if ($option->servers_count > 0) { + throw new DisplayException('You cannot delete a service option that has servers associated with it.'); + } + + DB::transaction(function () use ($option) { + foreach ($option->variables as $variable) { + (new VariableRepository)->delete($variable->id); + } + + $option->delete(); + }); + } + + /** + * Updates a service option in the database which can then be used + * on nodes. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\ServiceOption + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function update($id, array $data) + { + $option = ServiceOption::findOrFail($id); + + // Due to code limitations (at least when I am writing this currently) + // we have to make an assumption that if config_from is not passed + // that we should be telling it that no config is wanted anymore. + // + // This really is only an issue if we open API access to this function, + // in which case users will always need to pass `config_from` in order + // to keep it assigned. + if (! isset($data['config_from']) && ! is_null($option->config_from)) { + $option->config_from = null; + } + + $validator = Validator::make($data, [ + 'name' => 'sometimes|required|string|max:255', + 'description' => 'sometimes|required|string', + 'tag' => 'sometimes|required|string|max:255|unique:service_options,tag,' . $option->id, + 'docker_image' => 'sometimes|required|string|max:255', + 'startup' => 'sometimes|required|string', + 'config_from' => 'sometimes|required|numeric|exists:service_options,id', + ]); + + $validator->sometimes([ + 'config_startup', 'config_logs', 'config_files', + ], 'required_without:config_from|json', function ($input) use ($option) { + return ! (! $input->config_from && ! is_null($option->config_from)); + }); + + $validator->sometimes('config_stop', 'required_without:config_from|string|max:255', function ($input) use ($option) { + return ! (! $input->config_from && ! is_null($option->config_from)); + }); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + if (isset($data['config_from'])) { + if (! ServiceOption::where('service_id', $option->service_id)->where('id', $data['config_from'])->first()) { + throw new DisplayException('The `configuration from` directive must be a child of the assigned service.'); + } + } + + $option->fill($data)->save(); + + return $option; + } + + /** + * Updates a service option's scripts in the database. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\ServiceOption + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function scripts($id, array $data) + { + $option = ServiceOption::findOrFail($id); + + $data['script_install'] = empty($data['script_install']) ? null : $data['script_install']; + + $validator = Validator::make($data, [ + 'script_install' => 'sometimes|nullable|string', + 'script_is_privileged' => 'sometimes|required|boolean', + 'script_entry' => 'sometimes|required|string', + 'script_container' => 'sometimes|required|string', + 'copy_script_from' => 'sometimes|nullable|numeric', + ]); + + if (isset($data['copy_script_from']) && ! empty($data['copy_script_from'])) { + $select = ServiceOption::whereNull('copy_script_from')->where([ + ['id', $data['copy_script_from']], + ['service_id', $option->service_id], + ])->first(); + + if (! $select) { + throw new DisplayException('The service option selected to copy a script from either does not exist, or is copying from a higher level.'); + } + } else { + $data['copy_script_from'] = null; + } + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + $option->fill($data)->save(); + + return $option; + } +} diff --git a/app/Repositories/PackRepository.php b/app/Repositories/PackRepository.php new file mode 100644 index 000000000..0a8854465 --- /dev/null +++ b/app/Repositories/PackRepository.php @@ -0,0 +1,239 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories; + +use DB; +use Uuid; +use Storage; +use Validator; +use Pterodactyl\Models\Pack; +use Pterodactyl\Services\UuidService; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +class PackRepository +{ + /** + * Creates a new pack on the system. + * + * @param array $data + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function create(array $data) + { + $validator = Validator::make($data, [ + 'name' => 'required|string', + 'version' => 'required|string', + 'description' => 'sometimes|nullable|string', + 'selectable' => 'sometimes|required|boolean', + 'visible' => 'sometimes|required|boolean', + 'locked' => 'sometimes|required|boolean', + 'option_id' => 'required|exists:service_options,id', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + if (isset($data['file_upload'])) { + if (! $data['file_upload']->isValid()) { + throw new DisplayException('The file provided does not appear to be valid.'); + } + + if (! in_array($data['file_upload']->getMimeType(), ['application/gzip', 'application/x-gzip'])) { + throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetype of application/gzip.'); + } + } + + return DB::transaction(function () use ($data) { + $uuid = new UuidService(); + + $pack = new Pack; + $pack->uuid = $uuid->generate('packs', 'uuid'); + $pack->fill([ + 'option_id' => $data['option_id'], + 'name' => $data['name'], + 'version' => $data['version'], + 'description' => (empty($data['description'])) ? null : $data['description'], + 'selectable' => isset($data['selectable']), + 'visible' => isset($data['visible']), + 'locked' => isset($data['locked']), + ])->save(); + + if (! $pack->exists) { + throw new DisplayException('Model does not exist after creation. Did an event prevent it from saving?'); + } + + Storage::makeDirectory('packs/' . $pack->uuid); + if (isset($data['file_upload'])) { + $data['file_upload']->storeAs('packs/' . $pack->uuid, 'archive.tar.gz'); + } + + return $pack; + }); + } + + /** + * Creates a new pack on the system given a template file. + * + * @param array $data + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function createWithTemplate(array $data) + { + if (! isset($data['file_upload'])) { + throw new DisplayException('No template file was found submitted with this request.'); + } + + if (! $data['file_upload']->isValid()) { + throw new DisplayException('The file provided does not appear to be valid.'); + } + + if (! in_array($data['file_upload']->getMimeType(), [ + 'application/zip', + 'text/plain', + 'application/json', + ])) { + throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetypes of application/zip or application/json.'); + } + + if ($data['file_upload']->getMimeType() === 'application/zip') { + $zip = new \ZipArchive; + if (! $zip->open($data['file_upload']->path())) { + throw new DisplayException('The uploaded archive was unable to be opened.'); + } + + $isTar = $zip->locateName('archive.tar.gz'); + + if (! $zip->locateName('import.json') || ! $isTar) { + throw new DisplayException('This contents of the provided archive were in an invalid format.'); + } + + $json = json_decode($zip->getFromName('import.json')); + $pack = $this->create([ + 'name' => $json->name, + 'version' => $json->version, + 'description' => $json->description, + 'option_id' => $data['option_id'], + 'selectable' => $json->selectable, + 'visible' => $json->visible, + 'locked' => $json->locked, + ]); + + if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) { + $pack->delete(); + throw new DisplayException('Unable to extract the archive file to the correct location.'); + } + + $zip->close(); + + return $pack; + } else { + $json = json_decode(file_get_contents($data['file_upload']->path())); + + return $this->create([ + 'name' => $json->name, + 'version' => $json->version, + 'description' => $json->description, + 'option_id' => $data['option_id'], + 'selectable' => $json->selectable, + 'visible' => $json->visible, + 'locked' => $json->locked, + ]); + } + } + + /** + * Updates a pack on the system. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function update($id, array $data) + { + $validator = Validator::make($data, [ + 'name' => 'sometimes|required|string', + 'option_id' => 'sometimes|required|exists:service_options,id', + 'version' => 'sometimes|required|string', + 'description' => 'sometimes|string', + 'selectable' => 'sometimes|required|boolean', + 'visible' => 'sometimes|required|boolean', + 'locked' => 'sometimes|required|boolean', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + $pack = Pack::withCount('servers')->findOrFail($id); + + if ($pack->servers_count > 0 && (isset($data['option_id']) && (int) $data['option_id'] !== $pack->option_id)) { + throw new DisplayException('You cannot modify the associated option if servers are attached to a pack.'); + } + + $pack->fill([ + 'name' => isset($data['name']) ? $data['name'] : $pack->name, + 'option_id' => isset($data['option_id']) ? $data['option_id'] : $pack->option_id, + 'version' => isset($data['version']) ? $data['version'] : $pack->version, + 'description' => (empty($data['description'])) ? null : $data['description'], + 'selectable' => isset($data['selectable']), + 'visible' => isset($data['visible']), + 'locked' => isset($data['locked']), + ])->save(); + + return $pack; + } + + /** + * Deletes a pack and files from the system. + * + * @param int $id + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id) + { + $pack = Pack::withCount('servers')->findOrFail($id); + + if ($pack->servers_count > 0) { + throw new DisplayException('Cannot delete a pack from the system if servers are assocaited with it.'); + } + + DB::transaction(function () use ($pack) { + $pack->delete(); + Storage::deleteDirectory('packs/' . $pack->uuid); + }); + } +} diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 47b1a4a97..374de1b07 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,193 +21,209 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; -use Crypt; use DB; -use Debugbar; +use Crypt; use Validator; -use Log; - -use Pterodactyl\Models; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Pack; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Service; +use Pterodactyl\Models\Allocation; +use Pterodactyl\Models\ServiceOption; use Pterodactyl\Services\UuidService; +use Pterodactyl\Models\ServerVariable; +use Pterodactyl\Models\ServiceVariable; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\TransferException; use Pterodactyl\Services\DeploymentService; -use Pterodactyl\Notifications\ServerCreated; -use Pterodactyl\Events\ServerDeleted; - use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\AccountNotFoundException; use Pterodactyl\Exceptions\DisplayValidationException; class ServerRepository { - + /** + * An array of daemon permission to assign to this server. + * + * @var array + */ protected $daemonPermissions = [ - 's:*' + 's:*', ]; - public function __construct() - { - // - } - /** * Generates a SFTP username for a server given a server name. - * format: mumble_67c7a4b0 + * format: mumble_67c7a4b0. * - * @param string $name - * @param string $uuid + * @param string $name + * @param null|string $identifier * @return string */ - protected function generateSFTPUsername($name, $uuid = null) + protected function generateSFTPUsername($name, $identifier = null) { + if (is_null($identifier) || ! ctype_alnum($identifier)) { + $unique = str_random(8); + } else { + if (strlen($identifier) < 8) { + $unique = $identifier . str_random((8 - strlen($identifier))); + } else { + $unique = substr($identifier, 0, 8); + } + } - $uuid = is_null($uuid) ? str_random(8) : $uuid; - return strtolower(substr(preg_replace('/\s+/', '', $name), 0, 6) . '_' . $uuid); + // Filter the Server Name + $name = trim(preg_replace('/[^\w]+/', '', $name), '_'); + $name = (strlen($name) < 1) ? str_random(6) : $name; + return strtolower(substr($name, 0, 6) . '_' . $unique); } /** * Adds a new server to the system. - * @param array $data An array of data descriptors for creating the server. These should align to the columns in the database. - * @return integer + * + * @param array $data + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\AutoDeploymentException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function create(array $data) { - - // Validate Fields $validator = Validator::make($data, [ - 'owner' => 'bail|required', - 'name' => 'required|regex:/^([\w -]{4,35})$/', + 'user_id' => 'required|exists:users,id', + 'name' => 'required|regex:/^([\w .-]{1,200})$/', + 'description' => 'sometimes|nullable|string', 'memory' => 'required|numeric|min:0', 'swap' => 'required|numeric|min:-1', 'io' => 'required|numeric|min:10|max:1000', 'cpu' => 'required|numeric|min:0', 'disk' => 'required|numeric|min:0', - 'service' => 'bail|required|numeric|min:1|exists:services,id', - 'option' => 'bail|required|numeric|min:1|exists:service_options,id', + 'service_id' => 'required|numeric|min:1|exists:services,id', + 'option_id' => 'required|numeric|min:1|exists:service_options,id', + 'location_id' => 'required|numeric|min:1|exists:locations,id', + 'pack_id' => 'sometimes|nullable|numeric|min:0', + 'custom_container' => 'string', 'startup' => 'string', - 'custom_image_name' => 'required_if:use_custom_image,on', - 'auto_deploy' => 'sometimes|boolean' + 'auto_deploy' => 'sometimes|required|accepted', + 'custom_id' => 'sometimes|required|numeric|unique:servers,id', + 'skip_scripts' => 'sometimes|required|boolean', ]); - $validator->sometimes('node', 'bail|required|numeric|min:1|exists:nodes,id', function ($input) { - return !($input->auto_deploy); + $validator->sometimes('node_id', 'required|numeric|min:1|exists:nodes,id', function ($input) { + return ! ($input->auto_deploy); }); - $validator->sometimes('ip', 'required|ip', function ($input) { - return (!$input->auto_deploy && !$input->allocation); + $validator->sometimes('allocation_id', 'required|numeric|exists:allocations,id', function ($input) { + return ! ($input->auto_deploy); }); - $validator->sometimes('port', 'required|numeric|min:1|max:65535', function ($input) { - return (!$input->auto_deploy && !$input->allocation); + $validator->sometimes('allocation_additional.*', 'sometimes|required|numeric|exists:allocations,id', function ($input) { + return ! ($input->auto_deploy); }); - $validator->sometimes('allocation', 'numeric|exists:allocations,id', function ($input) { - return !($input->auto_deploy || ($input->port && $input->ip)); - }); - - // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } - if (is_int($data['owner'])) { - $user = Models\User::select('id', 'email')->where('id', $data['owner'])->first(); - } else { - $user = Models\User::select('id', 'email')->where('email', $data['owner'])->first(); + $user = User::findOrFail($data['user_id']); + + $deployment = false; + if (isset($data['auto_deploy'])) { + $deployment = new DeploymentService; + + if (isset($data['location_id'])) { + $deployment->setLocation($data['location_id']); + } + + $deployment->setMemory($data['memory'])->setDisk($data['disk'])->select(); } - if (!$user) { - throw new DisplayException('The user id or email passed to the function was not found on the system.'); - } - - $autoDeployed = false; - if (isset($data['auto_deploy']) && in_array($data['auto_deploy'], [true, 1, "1"])) { - // This is an auto-deployment situation - // Ignore any other passed node data - unset($data['node'], $data['ip'], $data['port'], $data['allocation']); - - $autoDeployed = true; - $node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location']); - $allocation = DeploymentService::randomAllocation($node->id); - } else { - $node = Models\Node::getByID($data['node']); - } + $node = (! $deployment) ? Node::findOrFail($data['node_id']) : $deployment->node(); // Verify IP & Port are a.) free and b.) assigned to the node. // We know the node exists because of 'exists:nodes,id' in the validation - if (!$autoDeployed) { - if (!isset($data['allocation'])) { - $allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->where('node', $data['node'])->whereNull('assigned_to')->first(); - } else { - $allocation = Models\Allocation::where('id' , $data['allocation'])->where('node', $data['node'])->whereNull('assigned_to')->first(); - } + if (! $deployment) { + $allocation = Allocation::where('id', $data['allocation_id'])->where('node_id', $data['node_id'])->whereNull('server_id')->first(); + } else { + $allocation = $deployment->allocation(); } // Something failed in the query, either that combo doesn't exist, or it is in use. - if (!$allocation) { - throw new DisplayException('The selected IP/Port combination or Allocation ID is either already in use, or unavaliable for this node.'); + if (! $allocation) { + throw new DisplayException('The selected Allocation ID is either already in use, or unavaliable for this node.'); } // Validate those Service Option Variables // We know the service and option exists because of the validation. // We need to verify that the option exists for the service, and then check for // any required variable fields. (fields are labeled env_) - $option = Models\ServiceOptions::where('id', $data['option'])->where('parent_service', $data['service'])->first(); - if (!$option) { + $option = ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first(); + if (! $option) { throw new DisplayException('The requested service option does not exist for the specified service.'); } + // Validate the Pack + if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) { + $data['pack_id'] = null; + } else { + $pack = Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first(); + if (! $pack) { + throw new DisplayException('The requested service pack does not seem to exist for this combination.'); + } + } + // Load up the Service Information - $service = Models\Service::find($option->parent_service); + $service = Service::find($option->service_id); // Check those Variables - $variables = Models\ServiceVariables::where('option_id', $data['option'])->get(); + $variables = ServiceVariable::where('option_id', $data['option_id'])->get(); $variableList = []; if ($variables) { - foreach($variables as $variable) { + foreach ($variables as $variable) { // Is the variable required? - if (!$data['env_' . $variable->env_variable]) { - if ($variable->required === 1) { + if (! isset($data['env_' . $variable->env_variable])) { + if ($variable->required) { throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.'); } - $variableList = array_merge($variableList, [[ + $variableList[] = [ 'id' => $variable->id, 'env' => $variable->env_variable, - 'val' => $variable->default_value - ]]); + 'val' => $variable->default_value, + ]; continue; } // Check aganist Regex Pattern - if (!is_null($variable->regex) && !preg_match($variable->regex, $data['env_' . $variable->env_variable])) { + if (! is_null($variable->regex) && ! preg_match($variable->regex, $data['env_' . $variable->env_variable])) { throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').'); } - $variableList = array_merge($variableList, [[ + $variableList[] = [ 'id' => $variable->id, 'env' => $variable->env_variable, - 'val' => $data['env_' . $variable->env_variable] - ]]); + 'val' => $data['env_' . $variable->env_variable], + ]; continue; } } // Check Overallocation - if (!$autoDeployed) { + if (! $deployment) { if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) { - - $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(); + $totals = Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first(); // Check memory limits if (is_numeric($node->memory_overallocate)) { $newMemory = $totals->memory + $data['memory']; $memoryLimit = ($node->memory * (1 + ($node->memory_overallocate / 100))); - if($newMemory > $memoryLimit) { + if ($newMemory > $memoryLimit) { throw new DisplayException('The amount of memory allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->memory_overallocate + 100) . '% of its assigned ' . $node->memory . 'Mb of memory (' . $memoryLimit . 'Mb) of which ' . (($totals->memory / $node->memory) * 100) . '% (' . $totals->memory . 'Mb) is in use already. By allocating this server the node would be at ' . (($newMemory / $node->memory) * 100) . '% (' . $newMemory . 'Mb) usage.'); } } @@ -216,7 +232,7 @@ class ServerRepository if (is_numeric($node->disk_overallocate)) { $newDisk = $totals->disk + $data['disk']; $diskLimit = ($node->disk * (1 + ($node->disk_overallocate / 100))); - if($newDisk > $diskLimit) { + if ($newDisk > $diskLimit) { throw new DisplayException('The amount of disk allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->disk_overallocate + 100) . '% of its assigned ' . $node->disk . 'Mb of disk (' . $diskLimit . 'Mb) of which ' . (($totals->disk / $node->disk) * 100) . '% (' . $totals->disk . 'Mb) is in use already. By allocating this server the node would be at ' . (($newDisk / $node->disk) * 100) . '% (' . $newDisk . 'Mb) usage.'); } } @@ -229,363 +245,335 @@ class ServerRepository $uuid = new UuidService; // Add Server to the Database - $server = new Models\Server; + $server = new Server; $genUuid = $uuid->generate('servers', 'uuid'); $genShortUuid = $uuid->generateShort('servers', 'uuidShort', $genUuid); + + if (isset($data['custom_id'])) { + $server->id = $data['custom_id']; + } + $server->fill([ 'uuid' => $genUuid, 'uuidShort' => $genShortUuid, - 'node' => $node->id, + 'node_id' => $node->id, 'name' => $data['name'], - 'suspended' => 0, - 'owner' => $user->id, + 'description' => $data['description'], + 'skip_scripts' => isset($data['skip_scripts']), + 'suspended' => false, + 'owner_id' => $user->id, 'memory' => $data['memory'], 'swap' => $data['swap'], 'disk' => $data['disk'], 'io' => $data['io'], 'cpu' => $data['cpu'], - 'oom_disabled' => (isset($data['oom_disabled'])) ? true : false, - 'allocation' => $allocation->id, - 'service' => $data['service'], - 'option' => $data['option'], + 'oom_disabled' => isset($data['oom_disabled']), + 'allocation_id' => $allocation->id, + 'service_id' => $data['service_id'], + 'option_id' => $data['option_id'], + 'pack_id' => $data['pack_id'], 'startup' => $data['startup'], 'daemonSecret' => $uuid->generate('servers', 'daemonSecret'), - 'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image, + 'image' => (isset($data['custom_container']) && ! empty($data['custom_container'])) ? $data['custom_container'] : $option->docker_image, 'username' => $this->generateSFTPUsername($data['name'], $genShortUuid), - 'sftp_password' => Crypt::encrypt('not set') + 'sftp_password' => Crypt::encrypt('not set'), ]); $server->save(); // Mark Allocation in Use - $allocation->assigned_to = $server->id; + $allocation->server_id = $server->id; $allocation->save(); - // Add Variables - $environmentVariables = []; - $environmentVariables = array_merge($environmentVariables, [ - 'STARTUP' => $data['startup'] - ]); - foreach($variableList as $item) { - $environmentVariables = array_merge($environmentVariables, [ - $item['env'] => $item['val'] - ]); - Models\ServerVariables::create([ + // Add Additional Allocations + if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { + foreach ($data['allocation_additional'] as $allocation) { + $model = Allocation::where('id', $allocation)->where('node_id', $data['node_id'])->whereNull('server_id')->first(); + if (! $model) { + continue; + } + + $model->server_id = $server->id; + $model->save(); + } + } + + foreach ($variableList as $item) { + ServerVariable::create([ 'server_id' => $server->id, 'variable_id' => $item['id'], - 'variable_value' => $item['val'] + 'variable_value' => $item['val'], ]); } - // Queue Notification Email - $user->notify((new ServerCreated([ - 'name' => $server->name, - 'memory' => $server->memory, - 'node' => $node->name, - 'service' => $service->name, - 'option' => $option->name, - 'uuidShort' => $server->uuidShort - ]))); + $environment = $this->parseVariables($server); + $server->load('allocation', 'allocations'); - $client = Models\Node::guzzleRequest($node->id); - $client->request('POST', '/servers', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret - ], + $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [ 'json' => [ 'uuid' => (string) $server->uuid, 'user' => $server->username, 'build' => [ 'default' => [ - 'ip' => $allocation->ip, - 'port' => (int) $allocation->port + 'ip' => $server->allocation->ip, + 'port' => $server->allocation->port, ], - 'ports' => [ - (string) $allocation->ip => [ (int) $allocation->port ] - ], - 'env' => $environmentVariables, + 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(), + 'env' => $environment->pluck('value', 'variable')->toArray(), 'memory' => (int) $server->memory, 'swap' => (int) $server->swap, 'io' => (int) $server->io, 'cpu' => (int) $server->cpu, 'disk' => (int) $server->disk, - 'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image + 'image' => $server->image, ], 'service' => [ - 'type' => $service->file, - 'option' => $option->tag + 'type' => $service->folder, + 'option' => $option->tag, + 'pack' => (isset($pack)) ? $pack->uuid : null, + 'skip_scripts' => $server->skip_scripts, ], 'keys' => [ - (string) $server->daemonSecret => $this->daemonPermissions + (string) $server->daemonSecret => $this->daemonPermissions, ], - 'rebuild' => false - ] + 'rebuild' => false, + 'start_on_completion' => isset($data['start_on_completion']), + ], ]); DB::commit(); - return $server->id; - } catch (\GuzzleHttp\Exception\TransferException $ex) { - DB::rollBack(); - throw new DisplayException('There was an error while attempting to connect to the daemon to add this server.', $ex); + + return $server; } catch (\Exception $ex) { DB::rollBack(); throw $ex; } - } /** - * [updateDetails description] - * @param integer $id - * @param array $data - * @return boolean + * Update the details for a server. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function updateDetails($id, array $data) { - $uuid = new UuidService; $resetDaemonKey = false; // Validate Fields $validator = Validator::make($data, [ - 'owner' => 'email|exists:users,email', - 'name' => 'regex:([\w -]{4,35})' + 'owner_id' => 'sometimes|required|integer|exists:users,id', + 'name' => 'sometimes|required|regex:([\w .-]{1,200})', + 'description' => 'sometimes|nullable|string', + 'reset_token' => 'sometimes|required|accepted', ]); // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } DB::beginTransaction(); try { - $server = Models\Server::findOrFail($id); - $owner = Models\User::findOrFail($server->owner); + $server = Server::with('user')->findOrFail($id); // Update daemon secret if it was passed. - if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $owner->email)) { + if (isset($data['reset_token']) || (isset($data['owner_id']) && (int) $data['owner_id'] !== $server->user->id)) { $oldDaemonKey = $server->daemonSecret; $server->daemonSecret = $uuid->generate('servers', 'daemonSecret'); $resetDaemonKey = true; } - // Update Server Owner if it was passed. - if (isset($data['owner']) && $data['owner'] !== $owner->email) { - $newOwner = Models\User::select('id')->where('email', $data['owner'])->first(); - $server->owner = $newOwner->id; - } - - // Update Server Name if it was passed. - if (isset($data['name'])) { - $server->name = $data['name']; - } - // Save our changes - $server->save(); + $server->fill($data)->save(); // Do we need to update? If not, return successful. - if (!$resetDaemonKey) { - DB::commit(); - return true; + if (! $resetDaemonKey) { + return DB::commit(); } - // If we need to update do it here. - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $res = $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret - ], + $res = $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'exceptions' => false, 'json' => [ 'keys' => [ (string) $oldDaemonKey => [], - (string) $server->daemonSecret => $this->daemonPermissions - ] - ] + (string) $server->daemonSecret => $this->daemonPermissions, + ], + ], ]); if ($res->getStatusCode() === 204) { DB::commit(); - return true; + + return $server; } else { throw new DisplayException('Daemon returned a a non HTTP/204 error code. HTTP/' + $res->getStatusCode()); } } catch (\Exception $ex) { DB::rollBack(); - Log::error($ex); - throw new DisplayException('An error occured while attempting to update this server\'s information.'); + throw $ex; } - } /** - * [updateContainer description] - * @param int $id - * @param array $data - * @return bool + * Update the container for a server. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function updateContainer($id, array $data) { $validator = Validator::make($data, [ - 'image' => 'required|string' + 'docker_image' => 'required|string', ]); // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } DB::beginTransaction(); try { - $server = Models\Server::findOrFail($id); + $server = Server::findOrFail($id); - $server->image = $data['image']; + $server->image = $data['docker_image']; $server->save(); - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'build' => [ - 'image' => $server->image - ] - ] + 'image' => $server->image, + ], + ], ]); DB::commit(); - return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { - DB::rollBack(); - throw new DisplayException('An error occured while attempting to update the container image.', $ex); + + return $server; } catch (\Exception $ex) { DB::rollBack(); throw $ex; } - } /** - * [changeBuild description] - * @param integer $id - * @param array $data - * @return boolean + * Update the build details for a server. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function changeBuild($id, array $data) { - $validator = Validator::make($data, [ - 'default' => [ - 'string', - 'regex:/^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5])):(\d{1,5})$/' - ], - 'add_additional' => 'nullable|array', - 'remove_additional' => 'nullable|array', - 'memory' => 'integer|min:0', - 'swap' => 'integer|min:-1', - 'io' => 'integer|min:10|max:1000', - 'cpu' => 'integer|min:0', - 'disk' => 'integer|min:0' + 'allocation_id' => 'sometimes|required|exists:allocations,id', + 'add_allocations' => 'sometimes|required|array', + 'remove_allocations' => 'sometimes|required|array', + 'memory' => 'sometimes|required|integer|min:0', + 'swap' => 'sometimes|required|integer|min:-1', + 'io' => 'sometimes|required|integer|min:10|max:1000', + 'cpu' => 'sometimes|required|integer|min:0', + 'disk' => 'sometimes|required|integer|min:0', ]); // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } DB::beginTransaction(); try { - $server = Models\Server::findOrFail($id); - $allocation = Models\Allocation::findOrFail($server->allocation); - + $server = Server::with('allocation', 'allocations')->findOrFail($id); $newBuild = []; + $newAllocations = []; - if (isset($data['default'])) { - list($ip, $port) = explode(':', $data['default']); - if ($ip !== $allocation->ip || (int) $port !== $allocation->port) { - $selection = Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->first(); - if (!$selection) { - throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.'); + if (isset($data['allocation_id'])) { + if ((int) $data['allocation_id'] !== $server->allocation_id) { + $selection = $server->allocations->where('id', $data['allocation_id'])->first(); + if (! $selection) { + throw new DisplayException('The requested default connection is not allocated to this server.'); } - $server->allocation = $selection->id; - $newBuild['default'] = [ - 'ip' => $ip, - 'port' => (int) $port - ]; + $server->allocation_id = $selection->id; + $newBuild['default'] = ['ip' => $selection->ip, 'port' => $selection->port]; - // Re-Run to keep updated for rest of function - $allocation = Models\Allocation::findOrFail($server->allocation); + $server->load('allocation'); } } $newPorts = false; - // Remove Assignments - if (isset($data['remove_additional'])) { - foreach ($data['remove_additional'] as $id => $combo) { - list($ip, $port) = explode(':', $combo); - // Invalid, not worth killing the whole thing, we'll just skip over it. - if (!filter_var($ip, FILTER_VALIDATE_IP) || !preg_match('/^(\d{1,5})$/', $port)) { - break; - } - - // Can't remove the assigned IP/Port combo - if ($ip === $allocation->ip && (int) $port === (int) $allocation->port) { - break; - } - - $newPorts = true; - Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->update([ - 'assigned_to' => null - ]); - } - } - + $firstNewAllocation = null; // Add Assignments - if (isset($data['add_additional'])) { - foreach ($data['add_additional'] as $id => $combo) { - list($ip, $port) = explode(':', $combo); - // Invalid, not worth killing the whole thing, we'll just skip over it. - if (!filter_var($ip, FILTER_VALIDATE_IP) || !preg_match('/^(\d{1,5})$/', $port)) { - break; - } - - // Don't allow double port assignments - if (Models\Allocation::where('port', $port)->where('assigned_to', $server->id)->count() !== 0) { - break; + if (isset($data['add_allocations'])) { + foreach ($data['add_allocations'] as $allocation) { + $model = Allocation::where('id', $allocation)->whereNull('server_id')->first(); + if (! $model) { + continue; } $newPorts = true; - Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('assigned_to')->update([ - 'assigned_to' => $server->id + $firstNewAllocation = $firstNewAllocation ?? $model; + $model->update([ + 'server_id' => $server->id, ]); } + + $server->load('allocations'); } - // Loop All Assignments - $additionalAssignments = []; - $assignments = Models\Allocation::where('assigned_to', $server->id)->get(); - foreach ($assignments as &$assignment) { - if (array_key_exists((string) $assignment->ip, $additionalAssignments)) { - array_push($additionalAssignments[ (string) $assignment->ip ], (int) $assignment->port); - } else { - $additionalAssignments[ (string) $assignment->ip ] = [ (int) $assignment->port ]; + // Remove Assignments + if (isset($data['remove_allocations'])) { + foreach ($data['remove_allocations'] as $allocation) { + // Can't remove the assigned IP/Port combo + if ((int) $allocation === $server->allocation_id) { + // No New Allocation + if (is_null($firstNewAllocation)) { + continue; + } + + // New Allocation, set as the default. + $server->allocation_id = $firstNewAllocation->id; + $newBuild['default'] = ['ip' => $firstNewAllocation->ip, 'port' => $firstNewAllocation->port]; + } + + $newPorts = true; + Allocation::where('id', $allocation)->where('server_id', $server->id)->update([ + 'server_id' => null, + ]); } + + $server->load('allocations'); } - if ($newPorts === true) { - $newBuild['ports|overwrite'] = $additionalAssignments; + if ($newPorts) { + $newBuild['ports|overwrite'] = $server->allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(); + + $newBuild['env|overwrite'] = $this->parseVariables($server)->pluck('value', 'variable')->toArray(); } // @TODO: verify that server can be set to this much memory without @@ -621,377 +609,428 @@ class ServerRepository // This won't be committed unless the HTTP request succeedes anyways $server->save(); - if (!empty($newBuild)) { - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret - ], + if (! empty($newBuild)) { + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ - 'build' => $newBuild - ] + 'build' => $newBuild, + ], ]); } DB::commit(); - return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { - DB::rollBack(); - throw new DisplayException('An error occured while attempting to update the configuration.', $ex); + + return $server; } catch (\Exception $ex) { DB::rollBack(); throw $ex; } - } + /** + * Process the variables for a server, and save to the database. + * + * @param \Pterodactyl\Models\Server $server + * @param array $data + * @param bool $admin + * @return \Illuminate\Support\Collection + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + protected function processVariables(Server $server, $data, $admin = false) + { + $server->load('option.variables'); + + if ($admin) { + $server->startup = $data['startup']; + $server->save(); + } + + if ($server->option->variables) { + foreach ($server->option->variables as &$variable) { + $set = isset($data['env_' . $variable->id]); + + // If user is not an admin and are trying to edit a non-editable field + // or an invisible field just silently skip the variable. + if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) { + continue; + } + + // Perform Field Validation + $validator = Validator::make([ + 'variable_value' => ($set) ? $data['env_' . $variable->id] : null, + ], [ + 'variable_value' => $variable->rules, + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode( + collect([ + 'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'], + ])->merge($validator->errors()->toArray()) + )); + } + + $svar = ServerVariable::firstOrNew([ + 'server_id' => $server->id, + 'variable_id' => $variable->id, + ]); + + // Set the value; if one was not passed set it to the default value + if ($set) { + $svar->variable_value = $data['env_' . $variable->id]; + + // Not passed, check if this record exists if so keep value, otherwise set default + } else { + $svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value; + } + + $svar->save(); + } + } + + return $this->parseVariables($server); + } + + /** + * Parse the variables and return in a standardized format. + * + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Support\Collection + */ + protected function parseVariables(Server $server) + { + // Reload Variables + $server->load('variables'); + + $parsed = $server->option->variables->map(function ($item, $key) use ($server) { + $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + + return [ + 'variable' => $item->env_variable, + 'value' => (! is_null($display)) ? $display : $item->default_value, + ]; + }); + + $merge = [[ + 'variable' => 'STARTUP', + 'value' => $server->startup, + ], [ + 'variable' => 'P_VARIABLE__LOCATION', + 'value' => $server->location->short, + ]]; + + $allocations = $server->allocations->where('id', '!=', $server->allocation_id); + $i = 0; + + foreach ($allocations as $allocation) { + $merge[] = [ + 'variable' => 'ALLOC_' . $i . '__PORT', + 'value' => $allocation->port, + ]; + + $i++; + } + + if ($parsed->count() === 0) { + return collect($merge); + } + + return $parsed->merge($merge); + } + + /** + * Update the startup details for a server. + * + * @param int $id + * @param array $data + * @param bool $admin + * @return bool + * + * @throws \GuzzleHttp\Exception\RequestException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ public function updateStartup($id, array $data, $admin = false) { + $server = Server::with('variables', 'option.variables')->findOrFail($id); + $hasServiceChanges = false; - $server = Models\Server::findOrFail($id); - - DB::beginTransaction(); - - try { - // Check the startup - if (isset($data['startup'])) { - $server->startup = $data['startup']; - $server->save(); - } - - // Check those Variables - $variables = Models\ServiceVariables::select( - 'service_variables.*', - DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_currentValue') - )->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id') - ->where('option_id', $server->option) - ->get(); - - $variableList = []; - if ($variables) { - foreach($variables as &$variable) { - // Move on if the new data wasn't even sent - if (!isset($data[$variable->env_variable])) { - $variableList = array_merge($variableList, [[ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => $variable->a_currentValue - ]]); - continue; - } - - // Update Empty but skip validation - if (empty($data[$variable->env_variable])) { - $variableList = array_merge($variableList, [[ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => null - ]]); - continue; - } - - // Is the variable required? - // @TODO: is this even logical to perform this check? - if (isset($data[$variable->env_variable]) && empty($data[$variable->env_variable])) { - if ($variable->required === 1) { - throw new DisplayException('A required service option variable field (' . $variable->env_variable . ') was included in this request but was left blank.'); - } - } - - // Variable hidden and/or not user editable - if (($variable->user_viewable === 0 || $variable->user_editable === 0) && !$admin) { - throw new DisplayException('A service option variable field (' . $variable->env_variable . ') does not exist or you do not have permission to edit it.'); - } - - // Check aganist Regex Pattern - if (!is_null($variable->regex) && !preg_match($variable->regex, $data[$variable->env_variable])) { - throw new DisplayException('Failed to validate service option variable field (' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').'); - } - - $variableList = array_merge($variableList, [[ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => $data[$variable->env_variable] - ]]); - } - } - - // Add Variables - $environmentVariables = []; - $environmentVariables = array_merge($environmentVariables, [ - 'STARTUP' => $server->startup + if ($admin) { + // User is an admin, lots of things to do here. + $validator = Validator::make($data, [ + 'startup' => 'required|string', + 'skip_scripts' => 'sometimes|required|boolean', + 'service_id' => 'required|numeric|min:1|exists:services,id', + 'option_id' => 'required|numeric|min:1|exists:service_options,id', + 'pack_id' => 'sometimes|nullable|numeric|min:0', ]); - foreach($variableList as $item) { - $environmentVariables = array_merge($environmentVariables, [ - $item['env'] => $item['val'] - ]); - // Update model or make a new record if it doesn't exist. - $model = Models\ServerVariables::firstOrNew([ - 'variable_id' => $item['id'], - 'server_id' => $server->id - ]); - $model->variable_value = $item['val']; - $model->save(); + if ((int) $data['pack_id'] < 1) { + $data['pack_id'] = null; } - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } - $client->request('PATCH', '/server', [ - 'headers' => [ + if ( + $server->service_id != $data['service_id'] || + $server->option_id != $data['option_id'] || + $server->pack_id != $data['pack_id'] + ) { + $hasServiceChanges = true; + } + } + + // If user isn't an administrator, this function is being access from the front-end + // Just try to update specific variables. + if (! $admin || ! $hasServiceChanges) { + return DB::transaction(function () use ($admin, $data, $server) { + $environment = $this->processVariables($server, $data, $admin); + + $server->node->guzzleClient([ 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret - ], + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ + 'json' => [ + 'build' => [ + 'env|overwrite' => $environment->pluck('value', 'variable')->toArray(), + ], + ], + ]); + + return false; + }); + } + + // Validate those Service Option Variables + // We know the service and option exists because of the validation. + // We need to verify that the option exists for the service, and then check for + // any required variable fields. (fields are labeled env_) + $option = ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first(); + if (! $option) { + throw new DisplayException('The requested service option does not exist for the specified service.'); + } + + // Validate the Pack + if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) { + $data['pack_id'] = null; + } else { + $pack = Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first(); + if (! $pack) { + throw new DisplayException('The requested service pack does not seem to exist for this combination.'); + } + } + + return DB::transaction(function () use ($admin, $data, $server) { + $server->installed = 0; + $server->service_id = $data['service_id']; + $server->option_id = $data['option_id']; + $server->pack_id = $data['pack_id']; + $server->skip_scripts = isset($data['skip_scripts']); + $server->save(); + + $server->variables->each->delete(); + + $server->load('service', 'pack'); + + // Send New Environment + $environment = $this->processVariables($server, $data, $admin); + + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('POST', '/server/reinstall', [ 'json' => [ 'build' => [ - 'env|overwrite' => $environmentVariables - ] - ] + 'env|overwrite' => $environment->pluck('value', 'variable')->toArray(), + ], + 'service' => [ + 'type' => $server->option->service->folder, + 'option' => $server->option->tag, + 'pack' => (! is_null($server->pack_id)) ? $server->pack->uuid : null, + 'skip_scripts' => $server->skip_scripts, + ], + ], ]); - DB::commit(); return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { - DB::rollBack(); - throw new DisplayException('An error occured while attempting to update the server configuration.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - + }); } - public function deleteServer($id, $force) + /** + * Delete a server from the system permanetly. + * + * @param int $id + * @param bool $force + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id, $force = false) { - $server = Models\Server::findOrFail($id); - DB::beginTransaction(); + $server = Server::with('node', 'allocations', 'variables')->findOrFail($id); + // Due to MySQL lockouts if the daemon response fails, we need to + // delete the server from the daemon first. If it succeedes and then + // MySQL fails, users just need to force delete the server. + // + // If this is a force delete, continue anyways. try { - if ($force === 'force' || $force === true) { - $server->installed = 3; - $server->save(); + $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + 'X-Access-Server' => $server->uuid, + ])->request('DELETE', '/servers'); + } catch (ClientException $ex) { + // Exception is thrown on 4XX HTTP errors, so catch and determine + // if we should continue, or if there is a permissions error. + // + // Daemon throws a 404 if the server doesn't exist, if that is returned + // continue with deletion, even if not a force deletion. + $response = $ex->getResponse(); + if ($ex->getResponse()->getStatusCode() !== 404 && ! $force) { + throw new DisplayException($ex->getMessage()); + } + } catch (TransferException $ex) { + if (! $force) { + throw new DisplayException($ex->getMessage()); } - - $server->delete(); - DB::commit(); - - event(new ServerDeleted($server->id)); } catch (\Exception $ex) { - DB::rollBack(); throw $ex; } - } - public function deleteNow($id, $force = false) { - $server = Models\Server::withTrashed()->findOrFail($id); - $node = Models\Node::findOrFail($server->node); + DB::transaction(function () use ($server) { + $server->allocations->each(function ($item) { + $item->server_id = null; + $item->save(); + }); - // Handle server being restored previously or - // an accidental queue. - if (!$server->trashed()) { - return; - } + $server->variables->each->delete(); - DB::beginTransaction(); - try { - // Unassign Allocations - Models\Allocation::where('assigned_to', $server->id)->update([ - 'assigned_to' => null - ]); + $server->load('subusers.permissions'); + $server->subusers->each(function ($subuser) { + $subuser->permissions->each->delete(); + $subuser->delete(); + }); - // Remove Variables - Models\ServerVariables::where('server_id', $server->id)->delete(); - - // Remove Permissions (Foreign Key requires before Subusers) - Models\Permission::where('server_id', $server->id)->delete(); - - // Remove SubUsers - Models\Subuser::where('server_id', $server->id)->delete(); - - // Remove Downloads - Models\Download::where('server', $server->uuid)->delete(); - - // Clear Tasks - Models\Task::where('server', $server->id)->delete(); + $server->tasks->each->delete(); // Delete Databases // This is the one un-recoverable point where // transactions will not save us. $repository = new DatabaseRepository; - foreach(Models\Database::select('id')->where('server_id', $server->id)->get() as &$database) { - $repository->drop($database->id); - } + $server->databases->each(function ($item) use ($repository) { + $repository->drop($item->id); + }); - $client = Models\Node::guzzleRequest($server->node); - $client->request('DELETE', '/servers', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - 'X-Access-Server' => $server->uuid - ] - ]); - - $server->forceDelete(); - DB::commit(); - } catch (\GuzzleHttp\Exception\TransferException $ex) { - // Set installed is set to 3 when force deleting. - if ($server->installed === 3 || $force) { - $server->forceDelete(); - DB::commit(); - } else { - DB::rollBack(); - throw $ex; - } - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - public function cancelDeletion($id) - { - $server = Models\Server::withTrashed()->findOrFail($id); - $server->restore(); - - $server->installed = 1; - $server->save(); + // Fully delete the server. + $server->delete(); + }); } + /** + * Toggle the install status of a serve. + * + * @param int $id + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ public function toggleInstall($id) { - $server = Models\Server::findOrFail($id); - if ($server->installed === 2) { - throw new DisplayException('This server was marked as having a failed install, you cannot override this.'); + $server = Server::findOrFail($id); + if ($server->installed > 1) { + throw new DisplayException('This server was marked as having a failed install or being deleted, you cannot override this.'); } - $server->installed = ($server->installed === 1) ? 0 : 1; + $server->installed = ! $server->installed; + return $server->save(); } /** - * Suspends a server instance making it unable to be booted or used by a user. - * @param integer $id - * @return boolean + * Suspends or unsuspends a server. + * + * @param int $id + * @param bool $unsuspend + * @return void */ - public function suspend($id, $deleted = false) + public function toggleAccess($id, $unsuspend = true) { - $server = ($deleted) ? Models\Server::withTrashed()->findOrFail($id) : Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); + $server = Server::with('node')->findOrFail($id); - DB::beginTransaction(); - - try { - - // Already suspended, no need to make more requests. - if ($server->suspended === 1) { + DB::transaction(function () use ($server, $unsuspend) { + if ( + (! $unsuspend && $server->suspended) || + ($unsuspend && ! $server->suspended) + ) { return true; } - $server->suspended = 1; + $server->suspended = ! $unsuspend; $server->save(); - $client = Models\Node::guzzleRequest($server->node); - $client->request('POST', '/server/suspend', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - 'X-Access-Server' => $server->uuid - ] - ]); - - return DB::commit(); - } catch (\GuzzleHttp\Exception\TransferException $ex) { - DB::rollBack(); - throw new DisplayException('An error occured while attempting to contact the remote daemon to suspend this server.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } + $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + 'X-Access-Server' => $server->uuid, + ])->request('POST', ($unsuspend) ? '/server/unsuspend' : '/server/suspend'); + }); } /** - * Unsuspends a server instance. - * @param integer $id - * @return boolean + * Updates the SFTP password for a server. + * + * @param int $id + * @param string $password + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ - public function unsuspend($id) - { - $server = Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); - - DB::beginTransaction(); - - try { - - // Already unsuspended, no need to make more requests. - if ($server->suspended === 0) { - return true; - } - - $server->suspended = 0; - $server->save(); - - $client = Models\Node::guzzleRequest($server->node); - $client->request('POST', '/server/unsuspend', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - 'X-Access-Server' => $server->uuid - ] - ]); - - return DB::commit(); - } catch (\GuzzleHttp\Exception\TransferException $ex) { - DB::rollBack(); - throw new DisplayException('An error occured while attempting to contact the remote daemon to un-suspend this server.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - public function updateSFTPPassword($id, $password) { - $server = Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); + $server = Server::with('node')->findOrFail($id); - $validator = Validator::make([ - 'password' => $password, - ], [ - 'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/' + $validator = Validator::make(['password' => $password], [ + 'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/', ]); if ($validator->fails()) { throw new DisplayValidationException(json_encode($validator->errors())); } - DB::beginTransaction(); - $server->sftp_password = Crypt::encrypt($password); - - try { + DB::transaction(function () use ($password, $server) { + $server->sftp_password = Crypt::encrypt($password); $server->save(); - $client = Models\Node::guzzleRequest($server->node); - $client->request('POST', '/server/password', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - 'X-Access-Server' => $server->uuid - ], - 'json' => [ - 'password' => $password, - ], + $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + 'X-Access-Server' => $server->uuid, + ])->request('POST', '/server/password', [ + 'json' => ['password' => $password], ]); - - DB::commit(); - return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { - DB::rollBack(); - throw new DisplayException('There was an error while attmping to contact the remote service to change the password.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - + }); } + /** + * Marks a server for reinstallation on the node. + * + * @param int $id + * @return void + */ + public function reinstall($id) + { + $server = Server::with('node')->findOrFail($id); + + DB::transaction(function () use ($server) { + $server->installed = 0; + $server->save(); + + $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + 'X-Access-Server' => $server->uuid, + ])->request('POST', '/server/reinstall'); + }); + } } diff --git a/app/Repositories/ServiceRepository.php b/app/Repositories/ServiceRepository.php new file mode 100644 index 000000000..a0d1716cc --- /dev/null +++ b/app/Repositories/ServiceRepository.php @@ -0,0 +1,135 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories; + +use DB; +use Validator; +use Pterodactyl\Models\Service; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +class ServiceRepository +{ + /** + * Creates a new service on the system. + * + * @param array $data + * @return \Pterodactyl\Models\Service + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function create(array $data) + { + $validator = Validator::make($data, [ + 'name' => 'required|string|min:1|max:255', + 'description' => 'required|nullable|string', + 'folder' => 'required|unique:services,folder|regex:/^[\w.-]{1,50}$/', + 'startup' => 'required|nullable|string', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + return DB::transaction(function () use ($data) { + $service = new Service; + $service->author = config('pterodactyl.service.author'); + $service->fill([ + 'name' => $data['name'], + 'description' => (isset($data['description'])) ? $data['description'] : null, + 'folder' => $data['folder'], + 'startup' => (isset($data['startup'])) ? $data['startup'] : null, + 'index_file' => Service::defaultIndexFile(), + ])->save(); + + // It is possible for an event to return false or throw an exception + // which won't necessarily be detected by this transaction. + // + // This check ensures the model was actually saved. + if (! $service->exists) { + throw new \Exception('Service model was created however the response appears to be invalid. Did an event fire wrongly?'); + } + + return $service; + }); + } + + /** + * Updates a service. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Service + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function update($id, array $data) + { + $service = Service::findOrFail($id); + + $validator = Validator::make($data, [ + 'name' => 'sometimes|required|string|min:1|max:255', + 'description' => 'sometimes|required|nullable|string', + 'folder' => 'sometimes|required|regex:/^[\w.-]{1,50}$/', + 'startup' => 'sometimes|required|nullable|string', + 'index_file' => 'sometimes|required|string', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + return DB::transaction(function () use ($data, $service) { + $service->fill($data)->save(); + + return $service; + }); + } + + /** + * Deletes a service and associated files and options. + * + * @param int $id + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id) + { + $service = Service::withCount('servers')->with('options')->findOrFail($id); + + if ($service->servers_count > 0) { + throw new DisplayException('You cannot delete a service that has servers associated with it.'); + } + + DB::transaction(function () use ($service) { + foreach ($service->options as $option) { + (new OptionRepository)->delete($option->id); + } + + $service->delete(); + }); + } +} diff --git a/app/Repositories/ServiceRepository/Option.php b/app/Repositories/ServiceRepository/Option.php deleted file mode 100644 index 60476b50e..000000000 --- a/app/Repositories/ServiceRepository/Option.php +++ /dev/null @@ -1,127 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Repositories\ServiceRepository; - -use DB; -use Validator; - -use Pterodactyl\Models; -use Pterodactyl\Services\UuidService; - -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class Option -{ - - public function __construct() - { - // - } - - public function create($service, array $data) - { - $service = Models\Service::findOrFail($service); - - $validator = Validator::make($data, [ - 'name' => 'required|string|max:255', - 'description' => 'required|string|min:1', - 'tag' => 'required|string|max:255', - 'executable' => 'sometimes|string|max:255', - 'docker_image' => 'required|string|max:255', - 'startup' => 'sometimes|string' - ]); - - if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); - } - - if (isset($data['executable']) && empty($data['executable'])) { - $data['executable'] = null; - } - - if (isset($data['startup']) && empty($data['startup'])) { - $data['startup'] = null; - } - - $option = new Models\ServiceOptions; - $option->parent_service = $service->id; - $option->fill($data); - $option->save(); - - return $option->id; - } - - public function delete($id) - { - $option = Models\ServiceOptions::findOrFail($id); - $servers = Models\Server::where('option', $option->id)->get(); - - if (count($servers) !== 0) { - throw new DisplayException('You cannot delete an option that has servers attached to it currently.'); - } - - DB::beginTransaction(); - - try { - Models\ServiceVariables::where('option_id', $option->id)->delete(); - $option->delete(); - - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - public function update($id, array $data) - { - $option = Models\ServiceOptions::findOrFail($id); - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|max:255', - 'description' => 'sometimes|required|string|min:1', - 'tag' => 'sometimes|required|string|max:255', - 'executable' => 'sometimes|string|max:255', - 'docker_image' => 'sometimes|required|string|max:255', - 'startup' => 'sometimes|string' - ]); - - if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); - } - - if (isset($data['executable']) && empty($data['executable'])) { - $data['executable'] = null; - } - - if (isset($data['startup']) && empty($data['startup'])) { - $data['startup'] = null; - } - - $option->fill($data); - $option->save(); - } - -} diff --git a/app/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php deleted file mode 100644 index 8feb92c1e..000000000 --- a/app/Repositories/ServiceRepository/Service.php +++ /dev/null @@ -1,113 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Repositories\ServiceRepository; - -use DB; -use Validator; -use Uuid; - -use Pterodactyl\Models; -use Pterodactyl\Services\UuidService; - -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class Service -{ - - public function __construct() - { - // - } - - public function create(array $data) - { - $validator = Validator::make($data, [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'required|string', - 'file' => 'required|regex:/^[\w.-]{1,50}$/', - 'executable' => 'max:255|regex:/^(.*)$/', - 'startup' => 'string' - ]); - - if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); - } - - if (Models\Service::where('file', $data['file'])->first()) { - throw new DisplayException('A service using that configuration file already exists on the system.'); - } - - $data['author'] = env('SERVICE_AUTHOR', (string) Uuid::generate(4)); - - $service = new Models\Service; - $service->fill($data); - $service->save(); - - return $service->id; - } - - public function update($id, array $data) - { - $service = Models\Service::findOrFail($id); - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|min:1|max:255', - 'description' => 'sometimes|required|string', - 'file' => 'sometimes|required|regex:/^[\w.-]{1,50}$/', - 'executable' => 'sometimes|max:255|regex:/^(.*)$/', - 'startup' => 'sometimes|string' - ]); - - if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); - } - - $service->fill($data); - $service->save(); - } - - public function delete($id) - { - $service = Models\Service::findOrFail($id); - $servers = Models\Server::where('service', $service->id)->get(); - $options = Models\ServiceOptions::select('id')->where('parent_service', $service->id); - - if (count($servers) !== 0) { - throw new DisplayException('You cannot delete a service that has servers associated with it.'); - } - - DB::beginTransaction(); - try { - Models\ServiceVariables::whereIn('option_id', $options->get()->toArray())->delete(); - $options->delete(); - $service->delete(); - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - -} diff --git a/app/Repositories/ServiceRepository/Variable.php b/app/Repositories/ServiceRepository/Variable.php deleted file mode 100644 index 154a8b061..000000000 --- a/app/Repositories/ServiceRepository/Variable.php +++ /dev/null @@ -1,133 +0,0 @@ - - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Repositories\ServiceRepository; - -use DB; -use Validator; - -use Pterodactyl\Models; -use Pterodactyl\Services\UuidService; - -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class Variable -{ - - public function __construct() - { - // - } - - public function create($id, array $data) - { - $option = Models\ServiceOptions::findOrFail($id); - - $validator = Validator::make($data, [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'required|string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/', - 'default_value' => 'string|max:255', - 'user_viewable' => 'sometimes|required|numeric|size:1', - 'user_editable' => 'sometimes|required|numeric|size:1', - 'required' => 'sometimes|required|numeric|size:1', - 'regex' => 'required|string|min:1' - ]); - - if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); - } - - if ($data['default_value'] !== '' && !preg_match($data['regex'], $data['default_value'])) { - throw new DisplayException('The default value you entered cannot violate the regex requirements.'); - } - - if (Models\ServiceVariables::where('env_variable', $data['env_variable'])->where('option_id', $option->id)->first()) { - throw new DisplayException('An environment variable with that name already exists for this option.'); - } - - $data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : 0; - $data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : 0; - $data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : 0; - - $variable = new Models\ServiceVariables; - $variable->option_id = $option->id; - $variable->fill($data); - $variable->save(); - } - - public function delete($id) { - $variable = Models\ServiceVariables::findOrFail($id); - - DB::beginTransaction(); - try { - Models\ServerVariables::where('variable_id', $variable->id)->delete(); - $variable->delete(); - - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - public function update($id, array $data) - { - $variable = Models\ServiceVariables::findOrFail($id); - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|min:1|max:255', - 'description' => 'sometimes|required|string', - 'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/', - 'default_value' => 'sometimes|string|max:255', - 'user_viewable' => 'sometimes|required|numeric|boolean', - 'user_editable' => 'sometimes|required|numeric|boolean', - 'required' => 'sometimes|required|numeric|boolean', - 'regex' => 'sometimes|required|string|min:1' - ]); - - if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); - } - - $data['default_value'] = (isset($data['default_value'])) ? $data['default_value'] : $variable->default_value; - $data['regex'] = (isset($data['regex'])) ? $data['regex'] : $variable->regex; - - if ($data['default_value'] !== '' && !preg_match($data['regex'], $data['default_value'])) { - throw new DisplayException('The default value you entered cannot violate the regex requirements.'); - } - - if (Models\ServiceVariables::where('id', '!=', $variable->id)->where('env_variable', $data['env_variable'])->where('option_id', $variable->option_id)->first()) { - throw new DisplayException('An environment variable with that name already exists for this option.'); - } - - $data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : $variable->user_viewable; - $data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : $variable->user_editable; - $data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : $variable->required; - - $variable->fill($data); - $variable->save(); - } - -} diff --git a/app/Repositories/SubuserRepository.php b/app/Repositories/SubuserRepository.php index 1e812a14a..7a2aef8cc 100644 --- a/app/Repositories/SubuserRepository.php +++ b/app/Repositories/SubuserRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,108 +21,51 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; use DB; -use Settings; use Validator; -use Mail; - -use Pterodactyl\Models; -use Pterodactyl\Repositories\UserRepository; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Models\Permission; use Pterodactyl\Services\UuidService; - -use Pterodactyl\Exceptions\DisplayValidationException; +use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; class SubuserRepository { - /** * Core permissions required for every subuser on the daemon. * Without this we cannot connect the websocket or get basic * information about the server. + * * @var array */ protected $coreDaemonPermissions = [ 's:get', - 's:console' + 's:console', ]; - /** - * Allowed permissions and their related daemon permission. - * @var array - */ - protected $permissions = [ - // Power Permissions - 'power-start' => 's:power:start', - 'power-stop' => 's:power:stop', - 'power-restart' => 's:power:restart', - 'power-kill' => 's:power:kill', - - // Commands - 'send-command' => 's:command', - - // File Manager - 'list-files' => 's:files:get', - 'edit-files' => 's:files:read', - 'save-files' => 's:files:post', - 'create-files' => 's:files:post', - 'download-files' => null, - 'upload-files' => 's:files:upload', - 'delete-files' => 's:files:delete', - 'move-files' => 's:files:move', - 'copy-files' => 's:files:copy', - 'compress-files' => 's:files:compress', - 'decompress-files' => 's:files:decompress', - - // Subusers - 'list-subusers' => null, - 'view-subuser' => null, - 'edit-subuser' => null, - 'create-subuser' => null, - 'delete-subuser' => null, - - // Tasks - 'list-tasks' => null, - 'view-task' => null, - 'toggle-task' => null, - 'delete-task' => null, - 'create-task' => null, - 'queue-task' => null, - - // Management - 'set-connection' => null, - 'view-startup' => null, - 'edit-startup' => null, - 'view-sftp' => null, - 'reset-sftp' => 's:set-password', - 'view-sftp-password' => null, - - // Databases - 'view-databases' => null, - 'reset-db-password' => null - ]; - - public function __construct() - { - // - } - /** * Creates a new subuser on the server. - * @param integer $id The ID of the server to add this subuser to. + * + * @param int $sid * @param array $data - * @throws DisplayValidationException - * @throws DisplayException - * @return integer Returns the ID of the newly created subuser. + * @return \Pterodactyl\Models\Subuser + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function create($sid, array $data) { - $server = Models\Server::findOrFail($sid); + $server = Server::with('node')->findOrFail($sid); + $validator = Validator::make($data, [ 'permissions' => 'required|array', - 'email' => 'required|email' + 'email' => 'required|email', ]); if ($validator->fails()) { @@ -133,42 +76,47 @@ class SubuserRepository try { // Determine if this user exists or if we need to make them an account. - $user = Models\User::where('email', $data['email'])->first(); - if (!$user) { - $password = str_random(16); + $user = User::where('email', $data['email'])->first(); + if (! $user) { try { $repo = new UserRepository; - $uid = $repo->create($data['email'], $password); - $user = Models\User::findOrFail($uid); + $user = $repo->create([ + 'email' => $data['email'], + 'username' => str_random(8), + 'name_first' => 'Unassigned', + 'name_last' => 'Name', + 'root_admin' => false, + ]); } catch (\Exception $ex) { throw $ex; } + } elseif ($server->owner_id === $user->id) { + throw new DisplayException('You cannot add the owner of a server as a subuser.'); + } elseif (Subuser::select('id')->where('user_id', $user->id)->where('server_id', $server->id)->first()) { + throw new DisplayException('A subuser with that email already exists for this server.'); } $uuid = new UuidService; - - $subuser = new Models\Subuser; - $subuser->fill([ + $subuser = Subuser::create([ 'user_id' => $user->id, 'server_id' => $server->id, - 'daemonSecret' => (string) $uuid->generate('servers', 'uuid') + 'daemonSecret' => (string) $uuid->generate('servers', 'uuid'), ]); - $subuser->save(); + $perms = Permission::listPermissions(true); $daemonPermissions = $this->coreDaemonPermissions; - foreach($data['permissions'] as $permission) { - if (array_key_exists($permission, $this->permissions)) { + + foreach ($data['permissions'] as $permission) { + if (array_key_exists($permission, $perms)) { // Build the daemon permissions array for sending. - if (!is_null($this->permissions[$permission])) { - array_push($daemonPermissions, $this->permissions[$permission]); + if (! is_null($perms[$permission])) { + array_push($daemonPermissions, $perms[$permission]); } - $model = new Models\Permission; - $model->fill([ - 'user_id' => $user->id, - 'server_id' => $server->id, - 'permission' => $permission + + Permission::create([ + 'subuser_id' => $subuser->id, + 'permission' => $permission, ]); - $model->save(); } } @@ -176,95 +124,81 @@ class SubuserRepository // We contact even if they don't have any daemon permissions to overwrite // if they did have them previously. - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $res = $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'keys' => [ - $subuser->daemonSecret => $daemonPermissions - ] - ] + $subuser->daemonSecret => $daemonPermissions, + ], + ], ]); - $email = $data['email']; - Mail::queue('emails.added-subuser', [ - 'serverName' => $server->name, - 'url' => route('server.index', $server->uuidShort), - ], function ($message) use ($email) { - $message->to($email); - $message->from(Settings::get('email_from', env('MAIL_FROM')), Settings::get('email_sender_name', env('MAIL_FROM_NAME', 'Pterodactyl Panel'))); - $message->subject(Settings::get('company') . ' - Added to Server'); - }); DB::commit(); - return $subuser->id; - } catch (\GuzzleHttp\Exception\TransferException $ex) { + + return $subuser; + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('There was an error attempting to connect to the daemon to add this user.', $ex); } catch (\Exception $ex) { DB::rollBack(); throw $ex; } + return false; } /** * Revokes a users permissions on a server. - * @param integer $id The ID of the subuser row in MySQL. - * @param array $data - * @throws DisplayValidationException - * @throws DisplayException + * + * @param int $id * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException */ public function delete($id) { - $subuser = Models\Subuser::findOrFail($id); - $server = Models\Server::findOrFail($subuser->server_id); + $subuser = Subuser::with('server.node')->findOrFail($id); + $server = $subuser->server; DB::beginTransaction(); try { - Models\Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); - - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $res = $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'keys' => [ - $subuser->daemonSecret => [] - ] - ] + $subuser->daemonSecret => [], + ], + ], ]); + foreach ($subuser->permissions as &$permission) { + $permission->delete(); + } $subuser->delete(); DB::commit(); - return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('There was an error attempting to connect to the daemon to delete this subuser.', $ex); } catch (\Exception $ex) { DB::rollBack(); throw $ex; } - return false; } /** * Updates permissions for a given subuser. - * @param integer $id The ID of the subuser row in MySQL. (Not the user ID) + * + * @param int $id * @param array $data - * @throws DisplayValidationException - * @throws DisplayException * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function update($id, array $data) { @@ -278,59 +212,53 @@ class SubuserRepository throw new DisplayValidationException(json_encode($validator->all())); } - $subuser = Models\Subuser::findOrFail($id); - $server = Models\Server::findOrFail($data['server']); + $subuser = Subuser::with('server.node')->findOrFail($id); + $server = $subuser->server; DB::beginTransaction(); try { - Models\Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); + foreach ($subuser->permissions as &$permission) { + $permission->delete(); + } + $perms = Permission::listPermissions(true); $daemonPermissions = $this->coreDaemonPermissions; - foreach($data['permissions'] as $permission) { - if (array_key_exists($permission, $this->permissions)) { + + foreach ($data['permissions'] as $permission) { + if (array_key_exists($permission, $perms)) { // Build the daemon permissions array for sending. - if (!is_null($this->permissions[$permission])) { - array_push($daemonPermissions, $this->permissions[$permission]); + if (! is_null($perms[$permission])) { + array_push($daemonPermissions, $perms[$permission]); } - $model = new Models\Permission; - $model->fill([ - 'user_id' => $data['user'], - 'server_id' => $data['server'], - 'permission' => $permission + Permission::create([ + 'subuser_id' => $subuser->id, + 'permission' => $permission, ]); - $model->save(); } } // Contact Daemon // We contact even if they don't have any daemon permissions to overwrite // if they did have them previously. - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $res = $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'keys' => [ - $subuser->daemonSecret => $daemonPermissions - ] - ] + $subuser->daemonSecret => $daemonPermissions, + ], + ], ]); DB::commit(); - return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('There was an error attempting to connect to the daemon to update permissions.', $ex); } catch (\Exception $ex) { DB::rollBack(); throw $ex; } - return false; } - } diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php index 5722ab929..24290e253 100644 --- a/app/Repositories/TaskRepository.php +++ b/app/Repositories/TaskRepository.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,19 +21,24 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; use Cron; use Validator; - -use Pterodactyl\Models; - -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Models\Task; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; class TaskRepository { - + /** + * The default values to use for new tasks. + * + * @var array + */ protected $defaults = [ 'year' => '*', 'day_of_week' => '*', @@ -43,65 +48,60 @@ class TaskRepository 'minute' => '*/30', ]; + /** + * Task action types. + * + * @var array + */ protected $actions = [ 'command', 'power', ]; - public function __construct() - { - // - } - /** * Deletes a given task. - * @param int $id * + * @param int $id * @return bool */ public function delete($id) { - $task = Models\Task::findOrFail($id); - try { - $task->delete(); - return true; - } catch (\Exception $ex) { - throw $ex; - } + $task = Task::findOrFail($id); + $task->delete(); } /** * Toggles a task active or inactive. - * @param int $id * - * @return int + * @param int $id + * @return bool */ public function toggle($id) { - $task = Models\Task::findOrFail($id); - try { - $task->active = ($task->active === 1) ? 0 : 1; - $task->queued = 0; - $task->save(); + $task = Task::findOrFail($id); - return $task->active; - } catch (\Exception $ex) { - throw $ex; - } + $task->active = ! $task->active; + $task->queued = false; + $task->save(); + + return $task->active; } /** * Create a new scheduled task for a given server. - * @param int $id - * @param array $data * - * @throws DisplayException - * @throws DisplayValidationException - * @return void + * @param int $server + * @param int $user + * @param array $data + * @return \Pterodactyl\Models\Task + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ - public function create($id, $data) + public function create($server, $user, $data) { - $server = Models\Server::findOrFail($id); + $server = Server::findOrFail($server); + $user = User::findOrFail($user); $validator = Validator::make($data, [ 'action' => 'string|required', @@ -111,20 +111,20 @@ class TaskRepository 'month' => 'string|sometimes', 'day_of_month' => 'string|sometimes', 'hour' => 'string|sometimes', - 'minute' => 'string|sometimes' + 'minute' => 'string|sometimes', ]); if ($validator->fails()) { throw new DisplayValidationException(json_encode($validator->errors())); } - if (!in_array($data['action'], $this->actions)) { + if (! in_array($data['action'], $this->actions)) { throw new DisplayException('The action provided is not valid.'); } $cron = $this->defaults; foreach ($this->defaults as $setting => $value) { - if (array_key_exists($setting, $data) && !is_null($data[$setting]) && $data[$setting] !== '') { + if (array_key_exists($setting, $data) && ! is_null($data[$setting]) && $data[$setting] !== '') { $cron[$setting] = $data[$setting]; } } @@ -143,9 +143,9 @@ class TaskRepository throw $ex; } - $task = new Models\Task; - $task->fill([ - 'server' => $server->id, + return Task::create([ + 'user_id' => $user->id, + 'server_id' => $server->id, 'active' => 1, 'action' => $data['action'], 'data' => $data['data'], @@ -157,11 +157,7 @@ class TaskRepository 'hour' => $cron['hour'], 'minute' => $cron['minute'], 'last_run' => null, - 'next_run' => $buildCron->getNextRunDate() + 'next_run' => $buildCron->getNextRunDate(), ]); - - return $task->save(); - } - } diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 01cad4269..1608afe9c 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -1,8 +1,8 @@ - * Some Modifications (c) 2015 Dylan Seidt + * Copyright (c) 2015 - 2017 Dane Everitt + * Some Modifications (c) 2015 Dylan Seidt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,58 +22,45 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories; use DB; -use Settings; -use Hash; -use Validator; -use Mail; -use Carbon; use Auth; - +use Hash; +use Settings; +use Validator; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; -use Pterodactyl\Notifications\AccountCreated; - -use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; class UserRepository { - - public function __construct() - { - // - } - /** * Creates a user on the panel. Returns the created user's ID. * - * @param string $email - * @param string|null $password An unhashed version of the user's password. - * @param bool $admin Boolean value if user should be an admin or not. - * @param int $token A custom user ID. - * @return bool|integer + * @param array $data + * @return \Pterodactyl\Models\User + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ - public function create($email, $password = null, $admin = false, $token = null) + public function create(array $data) { - $validator = Validator::make([ - 'email' => $email, - 'password' => $password, - 'root_admin' => $admin, - 'custom_id' => $token, - ], [ + $validator = Validator::make($data, [ 'email' => 'required|email|unique:users,email', - 'password' => 'nullable|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES, + 'name_first' => 'required|string|between:1,255', + 'name_last' => 'required|string|between:1,255', + 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, 'root_admin' => 'required|boolean', - 'custom_id' => 'nullable|unique:users,id', + 'custom_id' => 'sometimes|nullable|unique:users,id', ]); // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } DB::beginTransaction(); @@ -83,29 +70,27 @@ class UserRepository $uuid = new UuidService; // Support for API Services - if (!is_null($token)) { + if (isset($data['custom_id']) && ! is_null($data['custom_id'])) { $user->id = $token; } + // UUIDs are not mass-fillable. $user->uuid = $uuid->generate('users', 'uuid'); - $user->email = $email; - $user->password = Hash::make((is_null($password)) ? str_random(30) : $password); - $user->language = 'en'; - $user->root_admin = ($admin) ? 1 : 0; + + $user->fill([ + 'email' => $data['email'], + 'username' => $data['username'], + 'name_first' => $data['name_first'], + 'name_last' => $data['name_last'], + 'password' => (empty($data['password'])) ? 'unset' : Hash::make($data['password']), + 'root_admin' => $data['root_admin'], + 'language' => Settings::get('default_language', 'en'), + ]); $user->save(); - // Setup a Password Reset to use when they set a password. - $token = str_random(32); - DB::table('password_resets')->insert([ - 'email' => $user->email, - 'token' => $token, - 'created_at' => Carbon::now()->toDateTimeString() - ]); - - $user->notify((new AccountCreated($token))); - DB::commit(); - return $user->id; + + return $user; } catch (\Exception $ex) { DB::rollBack(); throw $ex; @@ -115,9 +100,11 @@ class UserRepository /** * Updates a user on the panel. * - * @param integer $id - * @param array $data An array of columns and their associated values to update for the user. - * @return boolean + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\User + * + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function update($id, array $data) { @@ -125,61 +112,71 @@ class UserRepository $validator = Validator::make($data, [ 'email' => 'sometimes|required|email|unique:users,email,' . $id, - 'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES, + 'name_first' => 'sometimes|required|string|between:1,255', + 'name_last' => 'sometimes|required|string|between:1,255', + 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, 'root_admin' => 'sometimes|required|boolean', 'language' => 'sometimes|required|string|min:1|max:5', 'use_totp' => 'sometimes|required|boolean', - 'totp_secret' => 'sometimes|required|size:16' + 'totp_secret' => 'sometimes|required|size:16', ]); // Run validator, throw catchable and displayable exception if it fails. // Exception includes a JSON result of failed validation rules. if ($validator->fails()) { - throw new DisplayValidationException($validator->errors()); + throw new DisplayValidationException(json_encode($validator->errors())); } - if(array_key_exists('password', $data)) { + // The password and root_admin fields are not mass assignable. + if (! empty($data['password'])) { $data['password'] = Hash::make($data['password']); + } else { + unset($data['password']); } - if (isset($data['password_confirmation'])) { - unset($data['password_confirmation']); - } + $user->fill($data)->save(); - $user->fill($data); - $user->save(); + return $user; } /** - * Deletes a user on the panel, returns the number of records deleted. + * Deletes a user on the panel. * - * @param integer $id - * @return integer + * @param int $id + * @return void + * @todo Move user self-deletion checking to the controller, rather than the repository. + * + * @throws \Pterodactyl\Exceptions\DisplayException */ public function delete($id) { - if(Models\Server::where('owner', $id)->count() > 0) { + $user = Models\User::findOrFail($id); + + if (Models\Server::where('owner_id', $id)->count() > 0) { throw new DisplayException('Cannot delete a user with active servers attached to thier account.'); } - // @TODO: this should probably be checked outside of this method because we won't always have Auth::user() - if(!is_null(Auth::user()) && Auth::user()->id === $id) { - throw new DisplayException('Cannot delete your own account.'); + if (! is_null(Auth::user()) && (int) Auth::user()->id === (int) $id) { + throw new DisplayException('Cannot delete your own account.'); } DB::beginTransaction(); try { - Models\Permission::where('user_id', $id)->delete(); - Models\Subuser::where('user_id', $id)->delete(); - Models\User::destroy($id); + foreach (Models\Subuser::with('permissions')->where('user_id', $id)->get() as &$subuser) { + foreach ($subuser->permissions as &$permission) { + $permission->delete(); + } + $subuser->delete(); + } + + $user->delete(); DB::commit(); - return true; } catch (\Exception $ex) { DB::rollBack(); throw $ex; } } - } diff --git a/app/Repositories/VariableRepository.php b/app/Repositories/VariableRepository.php new file mode 100644 index 000000000..1aded8293 --- /dev/null +++ b/app/Repositories/VariableRepository.php @@ -0,0 +1,170 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories; + +use DB; +use Validator; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +class VariableRepository +{ + /** + * Create a new service variable. + * + * @param int $option + * @param array $data + * @return \Pterodactyl\Models\ServiceVariable + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function create($option, array $data) + { + $option = ServiceOption::select('id')->findOrFail($option); + + $validator = Validator::make($data, [ + 'name' => 'required|string|min:1|max:255', + 'description' => 'sometimes|nullable|string', + 'env_variable' => 'required|regex:/^[\w]{1,255}$/', + 'default_value' => 'string', + 'options' => 'sometimes|required|array', + 'rules' => 'bail|required|string', + ]); + + // Ensure the default value is allowed by the rules provided. + $validator->sometimes('default_value', $data['rules'] ?? null, function ($input) { + return $input->default_value; + }); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + if (in_array($data['env_variable'], ServiceVariable::reservedNames())) { + throw new DisplayException('The environment variable name provided is a reserved keyword for the daemon.'); + } + + $search = ServiceVariable::where('env_variable', $data['env_variable'])->where('option_id', $option->id); + if ($search->first()) { + throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.'); + } + + if (! isset($data['options']) || ! is_array($data['options'])) { + $data['options'] = []; + } + + $data['option_id'] = $option->id; + $data['user_viewable'] = (in_array('user_viewable', $data['options'])); + $data['user_editable'] = (in_array('user_editable', $data['options'])); + + // Remove field that isn't used. + unset($data['options']); + + return ServiceVariable::create($data); + } + + /** + * Deletes a specified option variable as well as all server + * variables currently assigned. + * + * @param int $id + * @return void + */ + public function delete($id) + { + $variable = ServiceVariable::with('serverVariable')->findOrFail($id); + + DB::transaction(function () use ($variable) { + foreach ($variable->serverVariable as $v) { + $v->delete(); + } + + $variable->delete(); + }); + } + + /** + * Updates a given service variable. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\ServiceVariable + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function update($id, array $data) + { + $variable = ServiceVariable::findOrFail($id); + + $validator = Validator::make($data, [ + 'name' => 'required|string|min:1|max:255', + 'description' => 'nullable|string', + 'env_variable' => 'required|regex:/^[\w]{1,255}$/', + 'rules' => 'bail|required|string', + 'options' => 'sometimes|required|array', + ]); + + // Ensure the default value is allowed by the rules provided. + $rules = (isset($data['rules'])) ? $data['rules'] : $variable->rules; + $validator->sometimes('default_value', $rules, function ($input) { + return $input->default_value; + }); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + if (isset($data['env_variable'])) { + if (in_array($data['env_variable'], ServiceVariable::reservedNames())) { + throw new DisplayException('The environment variable name provided is a reserved keyword for the daemon.'); + } + + $search = ServiceVariable::where('env_variable', $data['env_variable']) + ->where('option_id', $variable->option_id) + ->where('id', '!=', $variable->id); + if ($search->first()) { + throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.'); + } + } + + if (! isset($data['options']) || ! is_array($data['options'])) { + $data['options'] = []; + } + + $data['user_viewable'] = (in_array('user_viewable', $data['options'])); + $data['user_editable'] = (in_array('user_editable', $data['options'])); + + // Remove field that isn't used. + unset($data['options']); + + $variable->fill($data)->save(); + + return $variable; + } +} diff --git a/app/Services/APILogService.php b/app/Services/APILogService.php index f1d8255be..c8e5c098c 100644 --- a/app/Services/APILogService.php +++ b/app/Services/APILogService.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,24 +21,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Services; use Log; - use Illuminate\Http\Request; use Pterodactyl\Models\APILog; class APILogService { - - public function __constructor() - { - // - } - + /** + * Log an API Request. + * + * @param \Illuminate\Http\Request $request + * @param null|string $error + * @param bool $authorized + * @return void + */ public static function log(Request $request, $error = null, $authorized = false) { - if ($request->bearerToken() && !empty($request->bearerToken())) { + if ($request->bearerToken() && ! empty($request->bearerToken())) { list($public, $hashed) = explode('.', $request->bearerToken()); } else { $public = null; @@ -53,7 +55,7 @@ class APILogService 'route' => $request->fullUrl(), 'content' => (empty($request->getContent())) ? null : $request->getContent(), 'user_agent' => $request->header('User-Agent'), - 'request_ip' => $request->ip() + 'request_ip' => $request->ip(), ]); $log->save(); } catch (\Exception $ex) { diff --git a/app/Services/DeploymentService.php b/app/Services/DeploymentService.php index 5d0c7d5e1..f25c04e93 100644 --- a/app/Services/DeploymentService.php +++ b/app/Services/DeploymentService.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,125 +21,238 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Services; use DB; - -use Pterodactyl\Models; -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Location; +use Pterodactyl\Exceptions\AutoDeploymentException; class DeploymentService { + /** + * Eloquent model representing the allocation to use. + * + * @var \Pterodactyl\Models\Allocation + */ + protected $allocation; - public function __constructor() + /** + * Amount of disk to be used by the server. + * + * @var int + */ + protected $disk; + + /** + * Amount of memory to be used by the sever. + * + * @var int + */ + protected $memory; + + /** + * Eloquent model representing the location to use. + * + * @var \Pterodactyl\Models\Location + */ + protected $location; + + /** + * Eloquent model representing the node to use. + * + * @var \Pterodactyl\Models\Node + */ + protected $node; + + /** + * Set the location to use when auto-deploying. + * + * @param int|\Pterodactyl\Models\Location $location + * @return void + */ + public function setLocation($location) { - // + $this->location = ($location instanceof Location) ? $location : Location::with('nodes')->findOrFail($location); + if (! $this->location->relationLoaded('nodes')) { + $this->location->load('nodes'); + } + + if (count($this->location->nodes) < 1) { + throw new AutoDeploymentException('The location provided does not contain any nodes and cannot be used.'); + } + + return $this; } /** - * Return a random location model. DO NOT USE. - * @return \Pterodactyl\Models\Node + * Set the node to use when auto-deploying. * - * @TODO Actually make this smarter. If we're selecting a random location - * but then it has no nodes we should probably continue attempting all locations - * until we hit one. - * - * Currently you should just pick a location and go from there. + * @param int|\Pterodactyl\Models\Node $node + * @return void */ - public static function randomLocation() + public function setNode($node) { - return Models\Location::inRandomOrder()->first(); + $this->node = ($node instanceof Node) ? $node : Node::findOrFail($node); + if (! $this->node->relationLoaded('allocations')) { + $this->node->load('allocations'); + } + + $this->setLocation($this->node->location); + + return $this; + } + + /** + * Set the amount of disk space to be used by the new server. + * + * @param int $disk + * @return void + */ + public function setDisk(int $disk) + { + $this->disk = $disk; + + return $this; + } + + /** + * Set the amount of memory to be used by the new server. + * + * @param int $memory + * @return void + */ + public function setMemory(int $memory) + { + $this->memory = $memory; + + return $this; + } + + /** + * Return a random location model. + * + * @param array $exclude + * @return void; + */ + protected function findLocation(array $exclude = []) + { + $location = Location::with('nodes')->whereNotIn('id', $exclude)->inRandomOrder()->first(); + + if (! $location) { + throw new AutoDeploymentException('Unable to locate a suitable location to select a node from.'); + } + + if (count($location->nodes) < 1) { + return $this->findLocation(array_merge($exclude, [$location->id])); + } + + $this->setLocation($location); } /** * Return a model instance of a random node. - * @param int $location - * @param array $not + * + * @return void; + */ + protected function findNode(array $exclude = []) + { + if (! $this->location) { + $this->setLocation($this->findLocation()); + } + + $select = $this->location->nodes->whereNotIn('id', $exclude); + if (count($select) < 1) { + throw new AutoDeploymentException('Unable to find a suitable node within the assigned location with enough space.'); + } + + // Check usage, select new node if necessary + $this->setNode($select->random()); + if (! $this->checkNodeUsage()) { + return $this->findNode(array_merge($exclude, [$this->node()->id])); + } + } + + /** + * Checks that a node's allocation limits will not be passed + * with the assigned limits. + * + * @return bool + */ + protected function checkNodeUsage() + { + if (! $this->disk && ! $this->memory) { + return true; + } + + $totals = Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $this->node()->id)->first(); + + if ($this->memory) { + $limit = ($this->node()->memory * (1 + ($this->node()->memory_overallocate / 100))); + + if (($totals->memory + $this->memory) > $limit) { + return false; + } + } + + if ($this->disk) { + $limit = ($this->node()->disk * (1 + ($this->node()->disk_overallocate / 100))); + + if (($totals->disk + $this->disk) > $limit) { + return false; + } + } + + return true; + } + + /** + * Return the assigned node for this auto-deployment. + * * @return \Pterodactyl\Models\Node + */ + public function node() + { + return $this->node; + } + + /** + * Return the assigned location for this auto-deployment. * - * @throws \Pterodactyl\Exceptions\DisplayException + * @return \Pterodactyl\Models\Location */ - public static function randomNode($location, array $not = []) + public function location() { - $useLocation = Models\Location::where('id', $location)->first(); - if (!$useLocation) { - throw new DisplayException("The location passed was not valid and could not be found."); - } - - $node = Models\Node::where('location', $useLocation->id)->where('public', 1)->whereNotIn('id', $not)->inRandomOrder()->first(); - if (!$node) { - throw new DisplayException("Unable to find a node in location {$useLocation->short} (id: {$useLocation->id}) that is available and has space."); - } - - return $node; + return $this->location; } /** - * Selects a random node ensuring it does not put the node - * over allocation limits. - * @param int $memory - * @param int $disk - * @param int $location - * @return \Pterodactyl\Models\Node; + * Return the assigned location for this auto-deployment. * - * @throws \Pterodactyl\Exceptions\DisplayException + * @return \Pterodactyl\Models\Allocation */ - public static function smartRandomNode($memory, $disk, $location = null) { - $node = self::randomNode($location); - $notIn = []; - do { - $return = self::checkNodeAllocation($node, $memory, $disk); - if (!$return) { - $notIn = array_merge($notIn, [ - $node->id - ]); - $node = self::randomNode($location, $notIn); - } - } while (!$return); - - return $node; + public function allocation() + { + return $this->allocation; } /** - * Returns a random allocation for a node. - * @param int $node - * @return \Models\Pterodactyl\Allocation; + * Select and return the node to be used by the auto-deployment system. + * + * @return void */ - public static function randomAllocation($node) + public function select() { - $allocation = Models\Allocation::where('node', $node)->whereNull('assigned_to')->inRandomOrder()->first(); - if (!$allocation) { - throw new DisplayException('No available allocation could be found for the assigned node.'); + if (! $this->node) { + $this->findNode(); } - return $allocation; - } - - /** - * Checks that a node's allocation limits will not be passed with the given information. - * @param \Pterodactyl\Models\Node $node - * @param int $memory - * @param int $disk - * @return bool Returns true if this information would not put the node over it's limit. - */ - protected static function checkNodeAllocation(Models\Node $node, $memory, $disk) - { - if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) { - $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(); - - // Check memory limits - if (is_numeric($node->memory_overallocate)) { - $limit = ($node->memory * (1 + ($node->memory_overallocate / 100))); - $memoryLimitReached = (($totals->memory + $memory) > $limit); - } - - // Check Disk Limits - if (is_numeric($node->disk_overallocate)) { - $limit = ($node->disk * (1 + ($node->disk_overallocate / 100))); - $diskLimitReached = (($totals->disk + $disk) > $limit); - } - - return (!$diskLimitReached && !$memoryLimitReached); + // Set the Allocation + $this->allocation = $this->node()->allocations->where('server_id', null)->random(); + if (! $this->allocation) { + throw new AutoDeploymentException('Unable to find a suitable allocation to assign to this server.'); } } } diff --git a/app/Services/UuidService.php b/app/Services/UuidService.php index 430c82f82..2b043731a 100644 --- a/app/Services/UuidService.php +++ b/app/Services/UuidService.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Services; use DB; @@ -28,65 +29,48 @@ use Uuid; class UuidService { - - /** - * Constructor - */ - public function __construct() - { - // - } - /** * Generate a unique UUID validating against specified table and column. - * Defaults to `users.uuid` + * Defaults to `users.uuid`. * - * @param string $table - * @param string $field - * @param integer $type The type of UUID to generate. + * @param string $table + * @param string $field + * @param int $type * @return string */ public function generate($table = 'users', $field = 'uuid', $type = 4) { - $return = false; do { - $uuid = Uuid::generate($type); - if (!DB::table($table)->where($field, $uuid)->exists()) { + if (! DB::table($table)->where($field, $uuid)->exists()) { $return = $uuid; } - - } while (!$return); + } while (! $return); return (string) $return; - } /** * Generates a ShortUUID code which is 8 characters long and is used for identifying servers in the system. * - * @param string $table - * @param string $field + * @param string $table + * @param string $field + * @param null|string $attachedUuid * @return string */ public function generateShort($table = 'servers', $field = 'uuidShort', $attachedUuid = null) { - $return = false; do { - $short = (is_null($attachedUuid)) ? substr(Uuid::generate(4), 0, 8) : substr($attachedUuid, 0, 8); $attachedUuid = null; - if (!DB::table($table)->where($field, $short)->exists()) { + if (! DB::table($table)->where($field, $short)->exists()) { $return = $short; } - - } while (!$return); + } while (! $return); return (string) $return; - } - } diff --git a/app/Services/VersionService.php b/app/Services/VersionService.php new file mode 100644 index 000000000..e3eaf8ade --- /dev/null +++ b/app/Services/VersionService.php @@ -0,0 +1,135 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services; + +use Cache; +use GuzzleHttp\Client; + +class VersionService +{ + /** + * The cached CDN response. + * + * @var object + */ + protected static $versions; + + /** + * Version constructor. + * + * @return void + */ + public function __construct() + { + self::$versions = Cache::remember('versions', config('pterodactyl.cdn.cache'), function () { + $client = new Client(); + + try { + $response = $client->request('GET', config('pterodactyl.cdn.url')); + + if ($response->getStatusCode() === 200) { + return json_decode($response->getBody()); + } else { + throw new \Exception('Invalid response code.'); + } + } catch (\Exception $ex) { + // Failed request, just return errored version. + return (object) [ + 'panel' => 'error', + 'daemon' => 'error', + 'discord' => 'https://pterodactyl.io/discord', + ]; + } + }); + } + + /** + * Return current panel version from CDN. + * + * @return string + */ + public static function getPanel() + { + return self::$versions->panel; + } + + /** + * Return current daemon version from CDN. + * + * @return string + */ + public static function getDaemon() + { + return self::$versions->daemon; + } + + /** + * Return Discord link from CDN. + * + * @return string + */ + public static function getDiscord() + { + return self::$versions->discord; + } + + /** + * Return current panel version. + * + * @return null|string + */ + public function getCurrentPanel() + { + return config('app.version'); + } + + /** + * Determine if panel is latest version. + * + * @return bool + */ + public static function isLatestPanel() + { + if (config('app.version') === 'canary') { + return true; + } + + return version_compare(config('app.version'), self::$versions->panel) >= 0; + } + + /** + * Determine if daemon is latest version. + * + * @return bool + */ + public static function isLatestDaemon($daemon) + { + if ($daemon === '0.0.0-canary') { + return true; + } + + return version_compare($daemon, self::$versions->daemon) >= 0; + } +} diff --git a/app/Transformers/Admin/AllocationTransformer.php b/app/Transformers/Admin/AllocationTransformer.php new file mode 100644 index 000000000..e7bd15c36 --- /dev/null +++ b/app/Transformers/Admin/AllocationTransformer.php @@ -0,0 +1,98 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Allocation; +use League\Fractal\TransformerAbstract; + +class AllocationTransformer extends TransformerAbstract +{ + /** + * The filter to be applied to this transformer. + * + * @var bool|string + */ + protected $filter; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @param bool $filter + * @return void + */ + public function __construct($request = false, $filter = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + $this->filter = $filter; + } + + /** + * Return a generic transformed allocation array. + * + * @return array + */ + public function transform(Allocation $allocation) + { + return $this->transformWithFilter($allocation); + } + + /** + * Determine which transformer filter to apply. + * + * @return array + */ + protected function transformWithFilter(Allocation $allocation) + { + if ($this->filter === 'server') { + return $this->transformForServer($allocation); + } + + return $allocation->toArray(); + } + + /** + * Transform the allocation to only return information not duplicated + * in the server response (discard node_id and server_id). + * + * @return array + */ + protected function transformForServer(Allocation $allocation) + { + return collect($allocation)->only('id', 'ip', 'ip_alias', 'port')->toArray(); + } +} diff --git a/app/Transformers/Admin/LocationTransformer.php b/app/Transformers/Admin/LocationTransformer.php new file mode 100644 index 000000000..f3fd95885 --- /dev/null +++ b/app/Transformers/Admin/LocationTransformer.php @@ -0,0 +1,102 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Location; +use League\Fractal\TransformerAbstract; + +class LocationTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = [ + 'nodes', + 'servers', + ]; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed pack array. + * + * @return array + */ + public function transform(Location $location) + { + return $location->toArray(); + } + + /** + * Return the nodes associated with this location. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeServers(Location $location) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { + return; + } + + return $this->collection($location->servers, new ServerTransformer($this->request), 'server'); + } + + /** + * Return the nodes associated with this location. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeNodes(Location $location) + { + if ($this->request && ! $this->request->apiKeyHasPermission('node-list')) { + return; + } + + return $this->collection($location->nodes, new NodeTransformer($this->request), 'node'); + } +} diff --git a/app/Transformers/Admin/NodeTransformer.php b/app/Transformers/Admin/NodeTransformer.php new file mode 100644 index 000000000..d18b64f23 --- /dev/null +++ b/app/Transformers/Admin/NodeTransformer.php @@ -0,0 +1,117 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Node; +use League\Fractal\TransformerAbstract; + +class NodeTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = [ + 'allocations', + 'location', + 'servers', + ]; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed pack array. + * + * @return array + */ + public function transform(Node $node) + { + return $node->toArray(); + } + + /** + * Return the nodes associated with this location. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeAllocations(Node $node) + { + if ($this->request && ! $this->request->apiKeyHasPermission('node-view')) { + return; + } + + return $this->collection($node->allocations, new AllocationTransformer($this->request), 'allocation'); + } + + /** + * Return the nodes associated with this location. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includeLocation(Node $node) + { + if ($this->request && ! $this->request->apiKeyHasPermission('location-list')) { + return; + } + + return $this->item($node->location, new LocationTransformer($this->request), 'location'); + } + + /** + * Return the nodes associated with this location. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeServers(Node $node) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { + return; + } + + return $this->collection($node->servers, new ServerTransformer($this->request), 'server'); + } +} diff --git a/app/Transformers/Admin/OptionTransformer.php b/app/Transformers/Admin/OptionTransformer.php new file mode 100644 index 000000000..5b86b53d6 --- /dev/null +++ b/app/Transformers/Admin/OptionTransformer.php @@ -0,0 +1,132 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\ServiceOption; +use League\Fractal\TransformerAbstract; + +class OptionTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = [ + 'service', + 'packs', + 'servers', + 'variables', + ]; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed service option array. + * + * @return array + */ + public function transform(ServiceOption $option) + { + return $option->toArray(); + } + + /** + * Return the parent service for this service option. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeService(ServiceOption $option) + { + if ($this->request && ! $this->request->apiKeyHasPermission('service-view')) { + return; + } + + return $this->item($option->service, new ServiceTransformer($this->request), 'service'); + } + + /** + * Return the packs associated with this service option. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includePacks(ServiceOption $option) + { + if ($this->request && ! $this->request->apiKeyHasPermission('pack-list')) { + return; + } + + return $this->collection($option->packs, new PackTransformer($this->request), 'pack'); + } + + /** + * Return the servers associated with this service option. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeServers(ServiceOption $option) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { + return; + } + + return $this->collection($option->servers, new ServerTransformer($this->request), 'server'); + } + + /** + * Return the variables for this service option. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeVariables(ServiceOption $option) + { + if ($this->request && ! $this->request->apiKeyHasPermission('option-view')) { + return; + } + + return $this->collection($option->variables, new ServiceVariableTransformer($this->request), 'variable'); + } +} diff --git a/app/Transformers/Admin/PackTransformer.php b/app/Transformers/Admin/PackTransformer.php new file mode 100644 index 000000000..8d059faaf --- /dev/null +++ b/app/Transformers/Admin/PackTransformer.php @@ -0,0 +1,106 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Pack; +use League\Fractal\TransformerAbstract; + +class PackTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = [ + 'option', + 'servers', + ]; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed pack array. + * + * @return array + */ + public function transform($pack) + { + if (! $pack instanceof Pack) { + return ['id' => null]; + } + + return $pack->toArray(); + } + + /** + * Return the packs associated with this service. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includeOption(Pack $pack) + { + if ($this->request && ! $this->request->apiKeyHasPermission('option-view')) { + return; + } + + return $this->item($pack->option, new OptionTransformer($this->request), 'option'); + } + + /** + * Return the packs associated with this service. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeServers(Pack $pack) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { + return; + } + + return $this->collection($pack->servers, new ServerTransformer($this->request), 'server'); + } +} diff --git a/app/Transformers/Admin/ServerTransformer.php b/app/Transformers/Admin/ServerTransformer.php new file mode 100644 index 000000000..4d94b7e10 --- /dev/null +++ b/app/Transformers/Admin/ServerTransformer.php @@ -0,0 +1,207 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Server; +use League\Fractal\TransformerAbstract; + +class ServerTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = [ + 'allocations', + 'user', + 'subusers', + 'pack', + 'service', + 'option', + 'variables', + 'location', + 'node', + ]; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed server array. + * + * @return array + */ + public function transform(Server $server) + { + return collect($server->toArray())->only($server->getTableColumns())->toArray(); + } + + /** + * Return a generic array of allocations for this server. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeAllocations(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { + return; + } + + return $this->collection($server->allocations, new AllocationTransformer($this->request, 'server'), 'allocation'); + } + + /** + * Return a generic array of data about subusers for this server. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeSubusers(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { + return; + } + + return $this->collection($server->subusers, new SubuserTransformer($this->request), 'subuser'); + } + + /** + * Return a generic array of data about subusers for this server. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includeUser(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('user-view')) { + return; + } + + return $this->item($server->user, new UserTransformer($this->request), 'user'); + } + + /** + * Return a generic array with pack information for this server. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includePack(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('pack-view')) { + return; + } + + return $this->item($server->pack, new PackTransformer($this->request), 'pack'); + } + + /** + * Return a generic array with service information for this server. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includeService(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('service-view')) { + return; + } + + return $this->item($server->service, new ServiceTransformer($this->request), 'service'); + } + + /** + * Return a generic array with service option information for this server. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includeOption(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('option-view')) { + return; + } + + return $this->item($server->option, new OptionTransformer($this->request), 'option'); + } + + /** + * Return a generic array of data about subusers for this server. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeVariables(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { + return; + } + + return $this->collection($server->variables, new ServerVariableTransformer($this->request), 'server_variable'); + } + + /** + * Return a generic array with pack information for this server. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includeLocation(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('location-list')) { + return; + } + + return $this->item($server->location, new LocationTransformer($this->request), 'location'); + } + + /** + * Return a generic array with pack information for this server. + * + * @return \Leauge\Fractal\Resource\Item|void + */ + public function includeNode(Server $server) + { + if ($this->request && ! $this->request->apiKeyHasPermission('node-view')) { + return; + } + + return $this->item($server->node, new NodeTransformer($this->request), 'node'); + } +} diff --git a/app/Transformers/Admin/ServerVariableTransformer.php b/app/Transformers/Admin/ServerVariableTransformer.php new file mode 100644 index 000000000..3211e0295 --- /dev/null +++ b/app/Transformers/Admin/ServerVariableTransformer.php @@ -0,0 +1,85 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\ServerVariable; +use League\Fractal\TransformerAbstract; + +class ServerVariableTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = ['parent']; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed server variable array. + * + * @return array + */ + public function transform(ServerVariable $variable) + { + return $variable->toArray(); + } + + /** + * Return the parent service variable data. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includeParent(ServerVariable $variable) + { + if ($this->request && ! $this->request->apiKeyHasPermission('option-view')) { + return; + } + + return $this->item($variable->variable, new ServiceVariableTransformer($this->request), 'variable'); + } +} diff --git a/app/Transformers/Admin/ServiceTransformer.php b/app/Transformers/Admin/ServiceTransformer.php new file mode 100644 index 000000000..2df1fc8cc --- /dev/null +++ b/app/Transformers/Admin/ServiceTransformer.php @@ -0,0 +1,117 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Service; +use League\Fractal\TransformerAbstract; + +class ServiceTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = [ + 'options', + 'servers', + 'packs', + ]; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed service array. + * + * @return array + */ + public function transform(Service $service) + { + return $service->toArray(); + } + + /** + * Return the the service options. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeOptions(Service $service) + { + if ($this->request && ! $this->request->apiKeyHasPermission('option-list')) { + return; + } + + return $this->collection($service->options, new OptionTransformer($this->request), 'option'); + } + + /** + * Return the servers associated with this service. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeServers(Service $service) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { + return; + } + + return $this->collection($service->servers, new ServerTransformer($this->request), 'server'); + } + + /** + * Return the packs associated with this service. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includePacks(Service $service) + { + if ($this->request && ! $this->request->apiKeyHasPermission('pack-list')) { + return; + } + + return $this->collection($service->packs, new PackTransformer($this->request), 'pack'); + } +} diff --git a/app/Transformers/Admin/ServiceVariableTransformer.php b/app/Transformers/Admin/ServiceVariableTransformer.php new file mode 100644 index 000000000..aa10428d9 --- /dev/null +++ b/app/Transformers/Admin/ServiceVariableTransformer.php @@ -0,0 +1,85 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use League\Fractal\TransformerAbstract; +use Pterodactyl\Models\ServiceVariable; + +class ServiceVariableTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = ['variables']; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed server variable array. + * + * @return array + */ + public function transform(ServiceVariable $variable) + { + return $variable->toArray(); + } + + /** + * Return the server variables associated with this variable. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeVariables(ServiceVariable $variable) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { + return; + } + + return $this->collection($variable->serverVariable, new ServerVariableTransformer($this->request), 'server_variable'); + } +} diff --git a/app/Transformers/Admin/SubuserTransformer.php b/app/Transformers/Admin/SubuserTransformer.php new file mode 100644 index 000000000..129da7ad3 --- /dev/null +++ b/app/Transformers/Admin/SubuserTransformer.php @@ -0,0 +1,77 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Models\Permission; +use League\Fractal\TransformerAbstract; + +class SubuserTransformer extends TransformerAbstract +{ + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed subuser array. + * + * @return array + */ + public function transform(Subuser $subuser) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { + return; + } + + return [ + 'id' => $subuser->id, + 'username' => $subuser->user->username, + 'email' => $subuser->user->email, + '2fa' => (bool) $subuser->user->use_totp, + 'permissions' => $subuser->permissions->pluck('permission'), + 'created_at' => $subuser->created_at, + 'updated_at' => $subuser->updated_at, + ]; + } +} diff --git a/app/Transformers/Admin/UserTransformer.php b/app/Transformers/Admin/UserTransformer.php new file mode 100644 index 000000000..0d26961b9 --- /dev/null +++ b/app/Transformers/Admin/UserTransformer.php @@ -0,0 +1,102 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Admin; + +use Illuminate\Http\Request; +use Pterodactyl\Models\User; +use League\Fractal\TransformerAbstract; + +class UserTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = [ + 'access', + 'servers', + ]; + + /** + * The Illuminate Request object if provided. + * + * @var \Illuminate\Http\Request|bool + */ + protected $request; + + /** + * Setup request object for transformer. + * + * @param \Illuminate\Http\Request|bool $request + * @return void + */ + public function __construct($request = false) + { + if (! $request instanceof Request && $request !== false) { + throw new DisplayException('Request passed to constructor must be of type Request or false.'); + } + + $this->request = $request; + } + + /** + * Return a generic transformed subuser array. + * + * @return array + */ + public function transform(User $user) + { + return $user->toArray(); + } + + /** + * Return the servers associated with this user. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeServers(User $user) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { + return; + } + + return $this->collection($user->servers, new ServerTransformer($this->request), 'server'); + } + + /** + * Return the servers that this user can access. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeAccess(User $user) + { + if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { + return; + } + + return $this->collection($user->access()->get(), new ServerTransformer($this->request), 'server'); + } +} diff --git a/app/Services/NotificationService.php b/app/Transformers/User/AllocationTransformer.php similarity index 59% rename from app/Services/NotificationService.php rename to app/Transformers/User/AllocationTransformer.php index 0ae6192f9..0fd0be453 100644 --- a/app/Services/NotificationService.php +++ b/app/Transformers/User/AllocationTransformer.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,44 +21,44 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Services; + +namespace Pterodactyl\Transformers\User; use Pterodactyl\Models\Server; -use Pterodactyl\Models\User; - -use Pterodactyl\Notifications\Daemon; - -class NotificationService { +use Pterodactyl\Models\Allocation; +use League\Fractal\TransformerAbstract; +class AllocationTransformer extends TransformerAbstract +{ + /** + * Server eloquent model. + * + * @return \Pterodactyl\Models\Server + */ protected $server; - protected $user; - /** - * Daemon will pass an event name, this matches that event name with the notification to send. - * @var array + * Setup allocation transformer with access to server data. + * + * @return void */ - protected $types = [ - // 'crashed' => 'CrashNotification', - // 'started' => 'StartNotification', - // 'stopped' => 'StopNotification', - // 'rebuild' => 'RebuildNotification' - ]; - public function __construct(Server $server) { $this->server = $server; - $this->user = User::findOrFail($server->owner); } - public function pass(array $notification) + /** + * Return a generic transformed allocation array. + * + * @return array + */ + public function transform(Allocation $allocation) { - if (!$notification->type) { - return; - } - - if (class_exists($this->types[$notification->type]::class)) { - $user->notify(new $this->types[$notification->type]($notification->payload)); - } + return [ + 'id' => $allocation->id, + 'ip' => $allocation->alias, + 'port' => $allocation->port, + 'default' => ($allocation->id === $this->server->allocation_id), + ]; } } diff --git a/app/Transformers/User/OverviewTransformer.php b/app/Transformers/User/OverviewTransformer.php new file mode 100644 index 000000000..c8e1db9ed --- /dev/null +++ b/app/Transformers/User/OverviewTransformer.php @@ -0,0 +1,50 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\User; + +use Pterodactyl\Models\Server; +use League\Fractal\TransformerAbstract; + +class OverviewTransformer extends TransformerAbstract +{ + /** + * Return a generic transformed server array. + * + * @return array + */ + public function transform(Server $server) + { + return [ + 'id' => $server->uuidShort, + 'uuid' => $server->uuid, + 'name' => $server->name, + 'node' => $server->node->name, + 'ip' => $server->allocation->alias, + 'port' => $server->allocation->port, + 'service' => $server->service->name, + 'option' => $server->option->name, + ]; + } +} diff --git a/app/Transformers/User/ServerTransformer.php b/app/Transformers/User/ServerTransformer.php new file mode 100644 index 000000000..4e5aacb56 --- /dev/null +++ b/app/Transformers/User/ServerTransformer.php @@ -0,0 +1,100 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\User; + +use Pterodactyl\Models\Server; +use League\Fractal\TransformerAbstract; + +class ServerTransformer extends TransformerAbstract +{ + /** + * List of resources that can be included. + * + * @var array + */ + protected $availableIncludes = [ + 'allocations', + 'subusers', + 'stats', + ]; + + /** + * Return a generic transformed server array. + * + * @return array + */ + public function transform(Server $server) + { + return [ + 'id' => $server->uuidShort, + 'uuid' => $server->uuid, + 'name' => $server->name, + 'description' => $server->description, + 'node' => $server->node->name, + 'limits' => [ + 'memory' => $server->memory, + 'swap' => $server->swap, + 'disk' => $server->disk, + 'io' => $server->io, + 'cpu' => $server->cpu, + 'oom_disabled' => (bool) $server->oom_disabled, + ], + ]; + } + + /** + * Return a generic array of allocations for this server. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeAllocations(Server $server) + { + $allocations = $server->allocations; + + return $this->collection($allocations, new AllocationTransformer($server), 'allocation'); + } + + /** + * Return a generic array of subusers for this server. + * + * @return \Leauge\Fractal\Resource\Collection + */ + public function includeSubusers(Server $server) + { + $server->load('subusers.permissions', 'subusers.user'); + + return $this->collection($server->subusers, new SubuserTransformer, 'subuser'); + } + + /** + * Return a generic array of allocations for this server. + * + * @return \Leauge\Fractal\Resource\Item + */ + public function includeStats(Server $server) + { + return $this->item($server->guzzleClient(), new StatsTransformer, 'stat'); + } +} diff --git a/app/Transformers/User/StatsTransformer.php b/app/Transformers/User/StatsTransformer.php new file mode 100644 index 000000000..6b08ea8a5 --- /dev/null +++ b/app/Transformers/User/StatsTransformer.php @@ -0,0 +1,63 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\User; + +use GuzzleHttp\Client; +use League\Fractal\TransformerAbstract; +use GuzzleHttp\Exception\ConnectException; + +class StatsTransformer extends TransformerAbstract +{ + /** + * Return a generic transformed subuser array. + * + * @return array + */ + public function transform(Client $client) + { + try { + $res = $client->request('GET', '/server', ['http_errors' => false]); + + if ($res->getStatusCode() !== 200) { + return [ + 'error' => 'Error: HttpResponseException. Recieved non-200 HTTP status code from daemon: ' . $res->statusCode(), + ]; + } + + $json = json_decode($res->getBody()); + + return [ + 'id' => 1, + 'status' => $json->status, + 'resources' => $json->proc, + ]; + } catch (ConnectException $ex) { + return [ + 'error' => 'Error: ConnectException. Unable to contact the daemon to request server status.', + 'exception' => (config('app.debug')) ? $ex->getMessage() : null, + ]; + } + } +} diff --git a/app/Transformers/User/SubuserTransformer.php b/app/Transformers/User/SubuserTransformer.php new file mode 100644 index 000000000..e1a122f1f --- /dev/null +++ b/app/Transformers/User/SubuserTransformer.php @@ -0,0 +1,48 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\User; + +use Pterodactyl\Models\Subuser; +use Pterodactyl\Models\Permission; +use League\Fractal\TransformerAbstract; + +class SubuserTransformer extends TransformerAbstract +{ + /** + * Return a generic transformed subuser array. + * + * @return array + */ + public function transform(Subuser $subuser) + { + return [ + 'id' => $subuser->id, + 'username' => $subuser->user->username, + 'email' => $subuser->user->email, + '2fa' => (bool) $subuser->user->use_totp, + 'permissions' => $subuser->permissions->pluck('permission'), + ]; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 81378a886..f35c15929 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -12,7 +12,7 @@ */ $app = new Illuminate\Foundation\Application( - realpath(__DIR__.'/../') + realpath(__DIR__ . '/../') ); /* diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php index 383013796..72354a655 100644 --- a/bootstrap/autoload.php +++ b/bootstrap/autoload.php @@ -14,21 +14,4 @@ define('LARAVEL_START', microtime(true)); | */ -require __DIR__.'/../vendor/autoload.php'; - -/* -|-------------------------------------------------------------------------- -| Include The Compiled Class File -|-------------------------------------------------------------------------- -| -| To dramatically increase your application's performance, you may use a -| compiled class file which contains all of the classes commonly used -| by a request. The Artisan "optimize" is used to create this file. -| -*/ - -$compiledPath = __DIR__.'/cache/compiled.php'; - -if (file_exists($compiledPath)) { - require $compiledPath; -} +require __DIR__ . '/../vendor/autoload.php'; diff --git a/composer.json b/composer.json index 3569e3d1b..3eb02fb85 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "Pterodactyl Panel", + "name": "pterodactyl/panel", "description": "The free, open-source game management panel. Supporting Minecraft, Spigot, BungeeCord, and SRCDS servers.", "license": "MIT", "authors": [ @@ -11,30 +11,39 @@ } ], "require": { - "php": ">=5.6.4", - "laravel/framework": "5.3.21", - "barryvdh/laravel-debugbar": "2.2.3", - "doctrine/dbal": "2.5.5", - "guzzlehttp/guzzle": "6.2.2", - "pragmarx/google2fa": "1.0.1", - "webpatser/laravel-uuid": "2.0.1", - "prologue/alerts": "0.4.0", - "s1lentium/iptools": "1.1.0", + "php": ">=7.0.0", + "aws/aws-sdk-php": "3.29.7", + "barryvdh/laravel-debugbar": "2.4.0", + "daneeveritt/login-notifications": "1.0.0", + "doctrine/dbal": "2.5.12", "edvinaskrucas/settings": "2.0.0", - "igaster/laravel-theme": "1.1.3", - "nesbot/carbon": "1.21.0", - "mtdowling/cron-expression": "1.1.0", - "dingo/api": "1.0.0-beta6", - "aws/aws-sdk-php": "3.19.20", - "predis/predis": "1.1.1" + "ext-mbstring": "*", + "ext-zip": "*", + "ext-pdo_mysql": "*", + "fideloper/proxy": "3.3.3", + "guzzlehttp/guzzle": "6.2.3", + "igaster/laravel-theme": "1.16.0", + "laracasts/utilities": "2.1.0", + "laravel/framework": "5.4.27", + "laravel/tinker": "1.0.1", + "lord/laroute": "2.4.4", + "mtdowling/cron-expression": "1.2.0", + "nesbot/carbon": "1.22.1", + "nicolaslopezj/searchable": "1.9.5", + "pragmarx/google2fa": "1.0.1", + "predis/predis": "1.1.1", + "prologue/alerts": "0.4.1", + "s1lentium/iptools": "1.1.0", + "spatie/laravel-fractal": "4.0.1", + "webpatser/laravel-uuid": "2.0.1" }, "require-dev": { + "barryvdh/laravel-ide-helper": "^2.3", + "friendsofphp/php-cs-fixer": "1.*", "fzaninotto/faker": "~1.4", "mockery/mockery": "0.9.*", - "phpunit/phpunit": "~5.0", - "symfony/css-selector": "3.1.*", - "symfony/dom-crawler": "3.1.*", - "laravel/homestead": "3.0.*" + "phpunit/phpunit": "~5.7", + "sllh/php-cs-fixer-styleci-bridge": "^2.1" }, "autoload": { "classmap": [ @@ -45,43 +54,32 @@ } }, "autoload-dev": { - "classmap": [ - "tests/TestCase.php" - ] + "psr-4": { + "Tests\\": "tests/" + } }, "scripts": { - "post-root-package-install": [ - "php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + "pre-install-cmd": [ + "rm -f bootstrap/cache/services.php bootstrap/cache/compiled.php" ], - "post-create-project-cmd": [ - "php artisan key:generate" + "pre-update-cmd": [ + "rm -f bootstrap/cache/services.php bootstrap/cache/compiled.php" ], "post-install-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postInstall", - "php artisan optimize" + "php artisan optimize", + "php artisan config:cache" ], "post-update-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postUpdate", - "php artisan optimize" - ], - "setup-dev": [ - "composer install", - "php -r \"copy('.env.example', '.env');\"", - "php vendor/bin/homestead make --ip=192.168.10.32", - "sed -i.bak 's/homestead.app/pterodactyl.local/g' Homestead.yaml", - "rm Homestead.yaml.bak", - "php artisan key:generate" - ], - "setup": [ - "composer install --ansi --no-dev", - "php -r \"file_exists('.env') || copy('.env.example', '.env');\"", - "php artisan key:generate" - ], - "upgrade": [ - "composer update --ansi --no-dev" + "php artisan optimize", + "php artisan config:cache" ] }, + "prefer-stable": true, "config": { - "preferred-install": "dist" + "preferred-install": "dist", + "sort-packages": true, + "optimize-autoloader": true } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..4fcf88be1 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5760 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "c82fadb556adb7d7f4405c35dee2ef64", + "packages": [ + { + "name": "aws/aws-sdk-php", + "version": "3.29.7", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "76540001ff938c072db5367a7c945296984b999b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/76540001ff938c072db5367a7c945296984b999b", + "reference": "76540001ff938c072db5367a7c945296984b999b", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^5.3.1|^6.2.1", + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "^1.4.1", + "mtdowling/jmespath.php": "~2.2", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-json": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "nette/neon": "^2.3", + "phpunit/phpunit": "^4.8.35|^5.4.0", + "psr/cache": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2017-06-16T17:29:33+00:00" + }, + { + "name": "barryvdh/laravel-debugbar", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "de15d00a74696db62e1b4782474c27ed0c4fc763" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/de15d00a74696db62e1b4782474c27ed0c4fc763", + "reference": "de15d00a74696db62e1b4782474c27ed0c4fc763", + "shasum": "" + }, + "require": { + "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", + "maximebf/debugbar": "~1.13.0", + "php": ">=5.5.9", + "symfony/finder": "~2.7|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facade" + } + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "PHP Debugbar integration for Laravel", + "keywords": [ + "debug", + "debugbar", + "laravel", + "profiler", + "webprofiler" + ], + "time": "2017-06-01T17:46:08+00:00" + }, + { + "name": "christian-riesen/base32", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/ChristianRiesen/base32.git", + "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", + "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "satooshi/php-coveralls": "0.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Base32\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Riesen", + "email": "chris.riesen@gmail.com", + "homepage": "http://christianriesen.com", + "role": "Developer" + } + ], + "description": "Base32 encoder/decoder according to RFC 4648", + "homepage": "https://github.com/ChristianRiesen/base32", + "keywords": [ + "base32", + "decode", + "encode", + "rfc4648" + ], + "time": "2016-05-05T11:49:03+00:00" + }, + { + "name": "daneeveritt/login-notifications", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/DaneEveritt/login-notifications.git", + "reference": "a12e9b25e9a5e42d3f25c16579ba6dc2b8aba910" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaneEveritt/login-notifications/zipball/a12e9b25e9a5e42d3f25c16579ba6dc2b8aba910", + "reference": "a12e9b25e9a5e42d3f25c16579ba6dc2b8aba910", + "shasum": "" + }, + "require": { + "laravel/framework": "~5.3.0|~5.4.0", + "nesbot/carbon": "1.22.*", + "php": "^5.6|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "DaneEveritt\\LoginNotifications\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dane Everitt", + "email": "dane@daneeveritt.com" + } + ], + "description": "Login notifications for Laravel", + "keywords": [ + "email", + "events", + "laravel", + "login", + "notifications" + ], + "time": "2017-04-14T20:57:26+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + }, + "type": "project", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "time": "2014-10-24T07:27:01+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-02-24T16:22:25+00:00" + }, + { + "name": "doctrine/cache", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "shasum": "" + }, + "require": { + "php": "~5.5|~7.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2016-10-29T11:16:17+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03T10:49:41+00:00" + }, + { + "name": "doctrine/common", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "930297026c8009a567ac051fd545bf6124150347" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/930297026c8009a567ac051fd545bf6124150347", + "reference": "930297026c8009a567ac051fd545bf6124150347", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~5.6|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ], + "time": "2017-01-13T14:02:13+00:00" + }, + { + "name": "doctrine/dbal", + "version": "v2.5.12", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/7b9e911f9d8b30d43b96853dab26898c710d8f44", + "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44", + "shasum": "" + }, + "require": { + "doctrine/common": ">=2.4,<2.8-dev", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "symfony/console": "2.*||^3.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ], + "time": "2017-02-08T12:53:47+00:00" + }, + { + "name": "doctrine/inflector", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2015-11-06T14:35:42+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "edvinaskrucas/settings", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/edvinaskrucas/settings.git", + "reference": "23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/edvinaskrucas/settings/zipball/23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022", + "reference": "23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022", + "shasum": "" + }, + "require": { + "illuminate/console": "^5.2", + "illuminate/database": "^5.2", + "illuminate/filesystem": "^5.2", + "illuminate/support": "^5.2", + "php": "^5.5|^7.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Krucas\\Settings\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Edvinas Kručas", + "email": "edv.krucas@gmail.com" + } + ], + "description": "Persistent settings package for Laravel framework.", + "keywords": [ + "Settings", + "laravel", + "persistent settings" + ], + "time": "2016-01-19T13:50:39+00:00" + }, + { + "name": "erusev/parsedown", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01", + "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "time": "2017-03-29T16:04:15+00:00" + }, + { + "name": "fideloper/proxy", + "version": "3.3.3", + "source": { + "type": "git", + "url": "https://github.com/fideloper/TrustedProxy.git", + "reference": "985eac8f966c03b4d9503cad9b5e5a51d41ce477" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/985eac8f966c03b4d9503cad9b5e5a51d41ce477", + "reference": "985eac8f966c03b4d9503cad9b5e5a51d41ce477", + "shasum": "" + }, + "require": { + "illuminate/contracts": "~5.0", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/http": "~5.0", + "mockery/mockery": "~0.9.3", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Fideloper\\Proxy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Fidao", + "email": "fideloper@gmail.com" + } + ], + "description": "Set trusted proxies for Laravel", + "keywords": [ + "load balancing", + "proxy", + "trusted proxy" + ], + "time": "2017-05-31T12:50:41+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.2.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8d6c6cc55186db87b7dc5009827429ba4e9dc006", + "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-02-28T22:50:30+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "igaster/laravel-theme", + "version": "v1.16", + "source": { + "type": "git", + "url": "https://github.com/igaster/laravel-theme.git", + "reference": "7816c4497feb326d11447737e8477779a713fc96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igaster/laravel-theme/zipball/7816c4497feb326d11447737e8477779a713fc96", + "reference": "7816c4497feb326d11447737e8477779a713fc96", + "shasum": "" + }, + "require": { + "illuminate/support": ">=5.2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/database": "~5.2", + "orchestra/testbench": "~3.0", + "phpunit/phpunit": "~4.0", + "vlucas/phpdotenv": "~2.0" + }, + "suggest": { + "orchestra/asset": "Use '@css' and '@js' in Blade files" + }, + "type": "library", + "autoload": { + "psr-4": { + "igaster\\laravelTheme\\": "src/", + "igaster\\laravelTheme\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Giannis Gasteratos", + "email": "igasteratos@gmail.com" + } + ], + "description": "Laravel 5 Themes: Asset & Views folder per theme. Theme inheritance. Blade integration and more...", + "homepage": "https://github.com/igaster/laravel-theme", + "keywords": [ + "assets", + "blade", + "laravel-5", + "package", + "themes", + "views" + ], + "time": "2017-06-07T15:24:25+00:00" + }, + { + "name": "jakub-onderka/php-console-color", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/e0b393dacf7703fc36a4efc3df1435485197e6c1", + "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "0.*", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleColor": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com", + "homepage": "http://www.acci.cz" + } + ], + "time": "2014-04-08T15:00:19+00:00" + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.3.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "shasum": "" + }, + "require": { + "jakub-onderka/php-console-color": "~0.1", + "php": ">=5.3.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~0.5", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleHighlighter": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "time": "2015-04-20T18:58:01+00:00" + }, + { + "name": "laracasts/utilities", + "version": "2.1", + "source": { + "type": "git", + "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", + "reference": "4402a0ed774f8eb36ea7ba169341d9d5b6049378" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/4402a0ed774f8eb36ea7ba169341d9d5b6049378", + "reference": "4402a0ed774f8eb36ea7ba169341d9d5b6049378", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.0", + "php": ">=5.4.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laracasts\\Utilities\\JavaScript\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeffrey Way", + "email": "jeffrey@laracasts.com" + } + ], + "description": "Transform your PHP to JavaScript", + "keywords": [ + "javascript", + "laravel" + ], + "time": "2015-10-01T05:16:28+00:00" + }, + { + "name": "laravel/framework", + "version": "v5.4.27", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "66f5e1b37cbd66e730ea18850ded6dc0ad570404" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/66f5e1b37cbd66e730ea18850ded6dc0ad570404", + "reference": "66f5e1b37cbd66e730ea18850ded6dc0ad570404", + "shasum": "" + }, + "require": { + "doctrine/inflector": "~1.0", + "erusev/parsedown": "~1.6", + "ext-mbstring": "*", + "ext-openssl": "*", + "league/flysystem": "~1.0", + "monolog/monolog": "~1.11", + "mtdowling/cron-expression": "~1.0", + "nesbot/carbon": "~1.20", + "paragonie/random_compat": "~1.4|~2.0", + "php": ">=5.6.4", + "ramsey/uuid": "~3.0", + "swiftmailer/swiftmailer": "~5.4", + "symfony/console": "~3.2", + "symfony/debug": "~3.2", + "symfony/finder": "~3.2", + "symfony/http-foundation": "~3.2", + "symfony/http-kernel": "~3.2", + "symfony/process": "~3.2", + "symfony/routing": "~3.2", + "symfony/var-dumper": "~3.2", + "tijsverkoyen/css-to-inline-styles": "~2.2", + "vlucas/phpdotenv": "~2.2" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "tightenco/collect": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "~3.0", + "doctrine/dbal": "~2.5", + "mockery/mockery": "~0.9.4", + "pda/pheanstalk": "~3.0", + "phpunit/phpunit": "~5.7", + "predis/predis": "~1.0", + "symfony/css-selector": "~3.2", + "symfony/dom-crawler": "~3.2" + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).", + "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).", + "laravel/tinker": "Required to use the tinker console command (~1.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", + "nexmo/client": "Required to use the Nexmo transport (~1.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", + "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", + "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.2).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.2).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "time": "2017-06-15T19:08:25+00:00" + }, + { + "name": "laravel/tinker", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "7eb2e281395131897407285672ef5532e87e17f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/7eb2e281395131897407285672ef5532e87e17f9", + "reference": "7eb2e281395131897407285672ef5532e87e17f9", + "shasum": "" + }, + "require": { + "illuminate/console": "~5.1", + "illuminate/contracts": "~5.1", + "illuminate/support": "~5.1", + "php": ">=5.5.9", + "psy/psysh": "0.7.*|0.8.*", + "symfony/var-dumper": "~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (~5.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "time": "2017-06-01T16:31:26+00:00" + }, + { + "name": "league/flysystem", + "version": "1.0.40", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3828f0b24e2c1918bb362d57a53205d6dc8fde61", + "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "ext-fileinfo": "*", + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^2.2", + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-copy": "Allows you to use Copy.com storage", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2017-04-28T10:15:08+00:00" + }, + { + "name": "league/fractal", + "version": "0.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/fractal.git", + "reference": "d0445305e308d9207430680acfd580557b679ddc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/d0445305e308d9207430680acfd580557b679ddc", + "reference": "d0445305e308d9207430680acfd580557b679ddc", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "doctrine/orm": "^2.5", + "illuminate/contracts": "~5.0", + "mockery/mockery": "~0.9", + "pagerfanta/pagerfanta": "~1.0.0", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5", + "zendframework/zend-paginator": "~2.3" + }, + "suggest": { + "illuminate/pagination": "The Illuminate Pagination component.", + "pagerfanta/pagerfanta": "Pagerfanta Paginator", + "zendframework/zend-paginator": "Zend Framework Paginator" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.13-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Fractal\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Sturgeon", + "email": "me@philsturgeon.uk", + "homepage": "http://philsturgeon.uk/", + "role": "Developer" + } + ], + "description": "Handle the output of complex data structures ready for API output.", + "homepage": "http://fractal.thephpleague.com/", + "keywords": [ + "api", + "json", + "league", + "rest" + ], + "time": "2017-03-12T01:28:43+00:00" + }, + { + "name": "lord/laroute", + "version": "v2.4.4", + "source": { + "type": "git", + "url": "https://github.com/aaronlord/laroute.git", + "reference": "2adee9daa5491f1ad7b953fc01df36ebc7294c3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aaronlord/laroute/zipball/2adee9daa5491f1ad7b953fc01df36ebc7294c3e", + "reference": "2adee9daa5491f1ad7b953fc01df36ebc7294c3e", + "shasum": "" + }, + "require": { + "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", + "illuminate/console": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", + "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", + "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", + "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "dev-master", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lord\\Laroute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Lord", + "email": "hello@aaronlord.is" + } + ], + "description": "Access Laravels URL/Route helper functions, from JavaScript.", + "keywords": [ + "javascript", + "laravel", + "routes", + "routing" + ], + "time": "2017-02-08T11:05:52+00:00" + }, + { + "name": "maximebf/debugbar", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/afee79a236348e39a44cb837106b7c5b4897ac2a", + "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/maximebf/php-debugbar", + "keywords": [ + "debug", + "debugbar" + ], + "time": "2017-01-05T08:46:19+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.22.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "~5.3" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2017-03-13T07:08:03+00:00" + }, + { + "name": "mtdowling/cron-expression", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/mtdowling/cron-expression.git", + "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/9504fa9ea681b586028adaaa0877db4aecf32bad", + "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "time": "2017-01-23T04:29:33+00:00" + }, + { + "name": "mtdowling/jmespath.php", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2016-12-03T22:08:25+00:00" + }, + { + "name": "nesbot/carbon", + "version": "1.22.1", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", + "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/translation": "~2.6 || ~3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "~4.0 || ~5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.23-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2017-01-16T07:55:07+00:00" + }, + { + "name": "nicolaslopezj/searchable", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/nicolaslopezj/searchable.git", + "reference": "1351b1b21ae9be9e0f49f375f56488df839723d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nicolaslopezj/searchable/zipball/1351b1b21ae9be9e0f49f375f56488df839723d4", + "reference": "1351b1b21ae9be9e0f49f375f56488df839723d4", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/database": "4.2.x|~5.0", + "php": ">=5.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nicolaslopezj\\Searchable\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Lopez", + "email": "nicolaslopezj@me.com" + } + ], + "description": "Eloquent model search trait.", + "keywords": [ + "database", + "eloquent", + "laravel", + "model", + "search", + "searchable" + ], + "time": "2016-12-16T21:23:34+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "2b9e2f71b722f7c53918ab0c25f7646c2013f17d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2b9e2f71b722f7c53918ab0c25f7646c2013f17d", + "reference": "2b9e2f71b722f7c53918ab0c25f7646c2013f17d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2017-03-05T18:23:57+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.10", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-03-13T16:27:32+00:00" + }, + { + "name": "pragmarx/google2fa", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "b346dc138339b745c5831405d00cff7c1351aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/b346dc138339b745c5831405d00cff7c1351aa0d", + "reference": "b346dc138339b745c5831405d00cff7c1351aa0d", + "shasum": "" + }, + "require": { + "christian-riesen/base32": "~1.3", + "paragonie/random_compat": "~1.4|~2.0", + "php": ">=5.4", + "symfony/polyfill-php56": "~1.2" + }, + "require-dev": { + "phpspec/phpspec": "~2.1" + }, + "suggest": { + "bacon/bacon-qr-code": "Required to generate inline QR Codes." + }, + "type": "library", + "extra": { + "component": "package", + "frameworks": [ + "Laravel" + ], + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "Authentication", + "Two Factor Authentication", + "google2fa", + "laravel" + ], + "time": "2016-07-18T20:25:04+00:00" + }, + { + "name": "predis/predis", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2016-06-16T16:22:20+00:00" + }, + { + "name": "prologue/alerts", + "version": "0.4.1", + "source": { + "type": "git", + "url": "https://github.com/prologuephp/alerts.git", + "reference": "2a7184a9f39ab6f6dde85dbe0b9c783241bf52ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/prologuephp/alerts/zipball/2a7184a9f39ab6f6dde85dbe0b9c783241bf52ea", + "reference": "2a7184a9f39ab6f6dde85dbe0b9c783241bf52ea", + "shasum": "" + }, + "require": { + "illuminate/config": "~5", + "illuminate/session": "~5", + "illuminate/support": "~5", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Prologue\\Alerts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dries Vints", + "email": "dries.vints@gmail.com", + "homepage": "http://driesvints.com", + "role": "Maintainer" + } + ], + "description": "Prologue Alerts is a package that handles global site messages.", + "keywords": [ + "alerts", + "laravel", + "messages" + ], + "time": "2017-01-24T13:22:25+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.8.6", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "7028d6d525fb183d50b249b7c07598e3d386b27d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/7028d6d525fb183d50b249b7c07598e3d386b27d", + "reference": "7028d6d525fb183d50b249b7c07598e3d386b27d", + "shasum": "" + }, + "require": { + "dnoegel/php-xdg-base-dir": "0.1", + "jakub-onderka/php-console-highlighter": "0.3.*", + "nikic/php-parser": "~1.3|~2.0|~3.0", + "php": ">=5.3.9", + "symfony/console": "~2.3.10|^2.4.2|~3.0", + "symfony/var-dumper": "~2.7|~3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "hoa/console": "~3.16|~1.14", + "phpunit/phpunit": "~4.4|~5.0", + "symfony/finder": "~2.1|~3.0" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", + "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.8.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psy/functions.php" + ], + "psr-4": { + "Psy\\": "src/Psy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "time": "2017-06-04T10:34:20+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.6.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4ae32dd9ab8860a4bbd750ad269cba7f06f7934e", + "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.0|^2.0", + "php": "^5.4 || ^7.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "apigen/apigen": "^4.1", + "codeception/aspect-mock": "^1.0 | ^2.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.4", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|>=5.0 <5.4", + "satooshi/php-coveralls": "^0.6.1", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2017-03-26T20:37:53+00:00" + }, + { + "name": "s1lentium/iptools", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/S1lentium/IPTools.git", + "reference": "cb4843d4077872643b5d38d18b8591b4aaf605ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/S1lentium/IPTools/zipball/cb4843d4077872643b5d38d18b8591b4aaf605ea", + "reference": "cb4843d4077872643b5d38d18b8591b4aaf605ea", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "satooshi/php-coveralls": "~1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "IPTools\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Safarov Alisher", + "email": "alisher.safarov@outlook.com", + "homepage": "https://github.com/S1lentium" + } + ], + "description": "PHP Library for manipulating network addresses (IPv4 and IPv6)", + "keywords": [ + "IP", + "IP-Tools", + "cidr", + "ipv4", + "ipv6", + "network", + "subnet" + ], + "time": "2016-08-21T15:57:09+00:00" + }, + { + "name": "spatie/fractalistic", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/fractalistic.git", + "reference": "8f00c666a8b8dfb06f79286f97255e6ab1c89639" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/fractalistic/zipball/8f00c666a8b8dfb06f79286f97255e6ab1c89639", + "reference": "8f00c666a8b8dfb06f79286f97255e6ab1c89639", + "shasum": "" + }, + "require": { + "league/fractal": "^0.16.0", + "php": "^5.6|^7.0" + }, + "require-dev": { + "illuminate/pagination": "~5.3.0|~5.4.0", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Fractalistic\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A developer friendly wrapper around Fractal", + "homepage": "https://github.com/spatie/fractalistic", + "keywords": [ + "api", + "fractal", + "fractalistic", + "spatie", + "transform" + ], + "time": "2017-05-29T14:16:20+00:00" + }, + { + "name": "spatie/laravel-fractal", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-fractal.git", + "reference": "3b95780f5f3ca79e29d445a5df87eac9f7c7c053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/3b95780f5f3ca79e29d445a5df87eac9f7c7c053", + "reference": "3b95780f5f3ca79e29d445a5df87eac9f7c7c053", + "shasum": "" + }, + "require": { + "illuminate/contracts": "~5.1.0|~5.2.0|~5.3.0|~5.4.0", + "illuminate/support": "~5.1.0|~5.2.0|~5.3.0|~5.4.0", + "php": "^5.6|^7.0", + "spatie/fractalistic": "^2.0" + }, + "require-dev": { + "orchestra/testbench": "~3.2.0|3.3.0|~3.4.0", + "phpunit/phpunit": "^5.7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Fractal\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "An easy to use Fractal integration for Laravel applications", + "homepage": "https://github.com/spatie/laravel-fractal", + "keywords": [ + "api", + "fractal", + "laravel", + "laravel-fractal", + "lumen", + "spatie", + "transform" + ], + "time": "2017-05-05T19:01:43+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/9a06dc570a0367850280eefd3f1dc2da45aef517", + "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.org", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2017-05-01T15:54:03+00:00" + }, + { + "name": "symfony/console", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/70d2a29b2911cbdc91a7e268046c395278238b2e", + "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3", + "symfony/dependency-injection": "~3.3", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/filesystem": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2017-06-02T19:24:58+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "4d882dced7b995d5274293039370148e291808f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4d882dced7b995d5274293039370148e291808f2", + "reference": "4d882dced7b995d5274293039370148e291808f2", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2017-05-01T15:01:29+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/e9c50482841ef696e8fa1470d950a79c8921f45d", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2017-06-01T21:01:25+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "4054a102470665451108f9b59305c79176ef98f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4054a102470665451108f9b59305c79176ef98f0", + "reference": "4054a102470665451108f9b59305c79176ef98f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-06-04T18:15:29+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2017-06-01T21:01:25+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2017-06-05T13:06:51+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/be8280f7fa8e95b86514f1e1be997668a53b2888", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2017-06-06T03:59:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-09T14:24:12+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/bc0b7d6cb36b10cfabb170a3e359944a95174929", + "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-09T08:25:21+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", + "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2017-06-09T08:25:21+00:00" + }, + { + "name": "symfony/process", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2017-05-22T12:32:03+00:00" + }, + { + "name": "symfony/routing", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/39804eeafea5cca851946e1eed122eb94459fdb4", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2017-06-02T09:51:43+00:00" + }, + { + "name": "symfony/translation", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/dc3b2a0c6cfff60327ba1c043a82092735397543", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/yaml": "~3.3" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2017-05-22T07:42:36+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/347c4247a3e40018810b476fcd5dec36d46d08dc", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2017-06-02T09:10:29+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b", + "reference": "ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7", + "symfony/css-selector": "^2.7|~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|5.1.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "time": "2016-09-20T12:50:39+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause-Attribution" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2016-09-01T10:05:43+00:00" + }, + { + "name": "webpatser/laravel-uuid", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/webpatser/laravel-uuid.git", + "reference": "6ed2705775e3edf066b90e1292f76f157ec00507" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webpatser/laravel-uuid/zipball/6ed2705775e3edf066b90e1292f76f157ec00507", + "reference": "6ed2705775e3edf066b90e1292f76f157ec00507", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "fzaninotto/faker": "1.5.*", + "phpunit/phpunit": "4.7.*" + }, + "suggest": { + "paragonie/random_compat": "A random_bytes Php 5.x polyfill." + }, + "type": "library", + "autoload": { + "psr-0": { + "Webpatser\\Uuid": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christoph Kempen", + "email": "christoph@downsized.nl" + } + ], + "description": "Class to generate a UUID according to the RFC 4122 standard. Support for version 1, 3, 4 and 5 UUID are built-in.", + "homepage": "https://github.com/webpatser/uuid", + "keywords": [ + "UUID RFC4122" + ], + "time": "2016-05-09T09:22:18+00:00" + } + ], + "packages-dev": [ + { + "name": "barryvdh/laravel-ide-helper", + "version": "v2.3.2", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-ide-helper.git", + "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e82de98cef0d6597b1b686be0b5813a3a4bb53c5", + "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5", + "shasum": "" + }, + "require": { + "barryvdh/reflection-docblock": "^2.0.4", + "illuminate/console": "^5.0,<5.5", + "illuminate/filesystem": "^5.0,<5.5", + "illuminate/support": "^5.0,<5.5", + "php": ">=5.4.0", + "symfony/class-loader": "^2.3|^3.0" + }, + "require-dev": { + "doctrine/dbal": "~2.3", + "phpunit/phpunit": "4.*", + "scrutinizer/ocular": "~1.1", + "squizlabs/php_codesniffer": "~2.3" + }, + "suggest": { + "doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\LaravelIdeHelper\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", + "keywords": [ + "autocomplete", + "codeintel", + "helper", + "ide", + "laravel", + "netbeans", + "phpdoc", + "phpstorm", + "sublime" + ], + "time": "2017-02-22T12:27:33+00:00" + }, + { + "name": "barryvdh/reflection-docblock", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/ReflectionDocBlock.git", + "reference": "3dcbd98b5d9384a5357266efba8fd29884458e5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/3dcbd98b5d9384a5357266efba8fd29884458e5c", + "reference": "3dcbd98b5d9384a5357266efba8fd29884458e5c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0,<4.5" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Barryvdh": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2016-06-13T19:28:20+00:00" + }, + { + "name": "composer/semver", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "time": "2016-08-30T16:08:34+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v1.13.1", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "0ea4f7ed06ca55da1d8fc45da26ff87f261c4088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/0ea4f7ed06ca55da1d8fc45da26ff87f261c4088", + "reference": "0ea4f7ed06ca55da1d8fc45da26ff87f261c4088", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^5.3.6 || >=7.0 <7.2", + "sebastian/diff": "^1.1", + "symfony/console": "^2.3 || ^3.0", + "symfony/event-dispatcher": "^2.1 || ^3.0", + "symfony/filesystem": "^2.1 || ^3.0", + "symfony/finder": "^2.1 || ^3.0", + "symfony/process": "^2.3 || ^3.0", + "symfony/stopwatch": "^2.5 || ^3.0" + }, + "conflict": { + "hhvm": "<3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.5|^5", + "satooshi/php-coveralls": "^1.0" + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "Symfony\\CS\\": "Symfony/CS/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "time": "2016-12-01T00:05:05+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "shasum": "" + }, + "require": { + "php": "^5.3.3|^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2016-04-29T12:21:54+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "hamcrest" + ], + "files": [ + "hamcrest/Hamcrest.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2015-05-11T14:41:42+00:00" + }, + { + "name": "mockery/mockery", + "version": "0.9.9", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "6fdb61243844dc924071d3404bb23994ea0b6856" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856", + "reference": "6fdb61243844dc924071d3404bb23994ea0b6856", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~1.1", + "lib-pcre": ">=7.0", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/padraic/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2017-02-28T12:52:32+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-04-12T18:52:22+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27T11:43:31+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-09-30T07:12:33+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-11-25T06:54:22+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2017-03-02T20:05:34+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2016-10-03T07:40:28+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.11", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-02-27T10:12:30+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.20", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cb94a5f8c07a03c8b7527ed7468a2926203f58b", + "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0.3|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-05-22T07:42:55+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2016-12-08T20:27:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "sllh/php-cs-fixer-styleci-bridge", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Soullivaneuh/php-cs-fixer-styleci-bridge.git", + "reference": "2406ac6ab7a243e1b4d4f5f73c12e5905c629877" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Soullivaneuh/php-cs-fixer-styleci-bridge/zipball/2406ac6ab7a243e1b4d4f5f73c12e5905c629877", + "reference": "2406ac6ab7a243e1b4d4f5f73c12e5905c629877", + "shasum": "" + }, + "require": { + "composer/semver": "^1.0", + "doctrine/inflector": "^1.0", + "php": "^5.3 || ^7.0", + "sllh/styleci-fixers": "^3.0 || ^4.0", + "symfony/config": "^2.3 || ^3.0", + "symfony/console": "^2.3 || ^3.0", + "symfony/yaml": "^2.3 || ^3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.6.1", + "matthiasnoback/symfony-config-test": "^1.2", + "symfony/phpunit-bridge": "^2.7.4 || ^3.0", + "twig/twig": "^1.22" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "SLLH\\StyleCIBridge\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sullivan SENECHAL", + "email": "soullivaneuh@gmail.com" + } + ], + "description": "Auto configure PHP-CS-Fixer from StyleCI config file", + "keywords": [ + "PSR-1", + "PSR-2", + "PSR-4", + "StyleCI", + "configuration", + "laravel", + "php-cs-fixer", + "psr", + "symfony" + ], + "time": "2016-06-22T13:26:46+00:00" + }, + { + "name": "sllh/styleci-fixers", + "version": "v4.10.0", + "source": { + "type": "git", + "url": "https://github.com/Soullivaneuh/styleci-fixers.git", + "reference": "f2547f5cd465325bc7b996d53657189019e7ac05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Soullivaneuh/styleci-fixers/zipball/f2547f5cd465325bc7b996d53657189019e7ac05", + "reference": "f2547f5cd465325bc7b996d53657189019e7ac05", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0" + }, + "require-dev": { + "styleci/sdk": "^1.0", + "symfony/console": "^3.0", + "twig/twig": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "SLLH\\StyleCIFixers\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sullivan SENECHAL", + "email": "soullivaneuh@gmail.com" + } + ], + "description": "Auto configure PHP-CS-Fixer from StyleCI config file", + "keywords": [ + "StyleCI", + "configuration", + "php-cs-fixer" + ], + "time": "2017-05-10T08:16:59+00:00" + }, + { + "name": "symfony/class-loader", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/386a294d621576302e7cc36965d6ed53b8c73c4f", + "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ClassLoader Component", + "homepage": "https://symfony.com", + "time": "2017-06-02T09:51:43+00:00" + }, + { + "name": "symfony/config", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/35716d4904e0506a7a5a9bcf23f854aeb5719bca", + "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3", + "symfony/yaml": "~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2017-06-02T18:07:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "c709670bf64721202ddbe4162846f250735842c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c709670bf64721202ddbe4162846f250735842c0", + "reference": "c709670bf64721202ddbe4162846f250735842c0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2017-05-28T14:08:56+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2017-04-12T14:14:56+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/9752a30000a8ca9f4b34b5227d15d0101b96b063", + "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-06-02T22:05:06+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23T20:04:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=7.0.0", + "ext-mbstring": "*", + "ext-zip": "*", + "ext-pdo_mysql": "*" + }, + "platform-dev": [] +} diff --git a/config/api.php b/config/api.php deleted file mode 100644 index 2a847e7ac..000000000 --- a/config/api.php +++ /dev/null @@ -1,220 +0,0 @@ - env('API_STANDARDS_TREE', 'x'), - - /* - |-------------------------------------------------------------------------- - | API Subtype - |-------------------------------------------------------------------------- - | - | Your subtype will follow the standards tree you use when used in the - | "Accept" header to negotiate the content type and version. - | - | For example: Accept: application/x.SUBTYPE.v1+json - | - */ - - 'subtype' => env('API_SUBTYPE', ''), - - /* - |-------------------------------------------------------------------------- - | Default API Version - |-------------------------------------------------------------------------- - | - | This is the default version when strict mode is disabled and your API - | is accessed via a web browser. It's also used as the default version - | when generating your APIs documentation. - | - */ - - 'version' => env('API_VERSION', 'v1'), - - /* - |-------------------------------------------------------------------------- - | Default API Prefix - |-------------------------------------------------------------------------- - | - | A default prefix to use for your API routes so you don't have to - | specify it for each group. - | - */ - - 'prefix' => env('API_PREFIX', 'api'), - - /* - |-------------------------------------------------------------------------- - | Default API Domain - |-------------------------------------------------------------------------- - | - | A default domain to use for your API routes so you don't have to - | specify it for each group. - | - */ - - 'domain' => env('API_DOMAIN', null), - - /* - |-------------------------------------------------------------------------- - | Name - |-------------------------------------------------------------------------- - | - | When documenting your API using the API Blueprint syntax you can - | configure a default name to avoid having to manually specify - | one when using the command. - | - */ - - 'name' => env('API_NAME', null), - - /* - |-------------------------------------------------------------------------- - | Conditional Requests - |-------------------------------------------------------------------------- - | - | Globally enable conditional requests so that an ETag header is added to - | any successful response. Subsequent requests will perform a check and - | will return a 304 Not Modified. This can also be enabled or disabled - | on certain groups or routes. - | - */ - - 'conditionalRequest' => env('API_CONDITIONAL_REQUEST', true), - - /* - |-------------------------------------------------------------------------- - | Strict Mode - |-------------------------------------------------------------------------- - | - | Enabling strict mode will require clients to send a valid Accept header - | with every request. This also voids the default API version, meaning - | your API will not be browsable via a web browser. - | - */ - - 'strict' => env('API_STRICT', false), - - /* - |-------------------------------------------------------------------------- - | Debug Mode - |-------------------------------------------------------------------------- - | - | Enabling debug mode will result in error responses caused by thrown - | exceptions to have a "debug" key that will be populated with - | more detailed information on the exception. - | - */ - - 'debug' => env('API_DEBUG', false), - - /* - |-------------------------------------------------------------------------- - | Generic Error Format - |-------------------------------------------------------------------------- - | - | When some HTTP exceptions are not caught and dealt with the API will - | generate a generic error response in the format provided. Any - | keys that aren't replaced with corresponding values will be - | removed from the final response. - | - */ - - 'errorFormat' => [ - 'message' => ':message', - 'errors' => ':errors', - 'code' => ':code', - 'status_code' => ':status_code', - 'debug' => ':debug', - ], - - /* - |-------------------------------------------------------------------------- - | API Middleware - |-------------------------------------------------------------------------- - | - | Middleware that will be applied globally to all API requests. - | - */ - - 'middleware' => [ - - ], - - /* - |-------------------------------------------------------------------------- - | Authentication Providers - |-------------------------------------------------------------------------- - | - | The authentication providers that should be used when attempting to - | authenticate an incoming API request. - | - */ - - 'auth' => [ - 'custom' => 'Pterodactyl\Http\Middleware\APISecretToken', - ], - - /* - |-------------------------------------------------------------------------- - | Throttling / Rate Limiting - |-------------------------------------------------------------------------- - | - | Consumers of your API can be limited to the amount of requests they can - | make. You can create your own throttles or simply change the default - | throttles. - | - */ - - 'throttling' => [ - - ], - - /* - |-------------------------------------------------------------------------- - | Response Transformer - |-------------------------------------------------------------------------- - | - | Responses can be transformed so that they are easier to format. By - | default a Fractal transformer will be used to transform any - | responses prior to formatting. You can easily replace - | this with your own transformer. - | - */ - - 'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class), - - /* - |-------------------------------------------------------------------------- - | Response Formats - |-------------------------------------------------------------------------- - | - | Responses can be returned in multiple formats by registering different - | response formatters. You can also customize an existing response - | formatter. - | - */ - - 'defaultFormat' => env('API_DEFAULT_FORMAT', 'json'), - - 'formats' => [ - 'json' => Dingo\Api\Http\Response\Format\Json::class, - ], - -]; diff --git a/config/app.php b/config/app.php index 3093b43d0..c049872c5 100644 --- a/config/app.php +++ b/config/app.php @@ -4,7 +4,18 @@ return [ 'env' => env('APP_ENV', 'production'), - 'version' => env('APP_VERSION', 'v0.5.0'), + 'version' => env('APP_VERSION', '0.6.4'), + + /* + |-------------------------------------------------------------------------- + | Application Name + |-------------------------------------------------------------------------- + | + | This value is the name of your application. This value is used when the + | framework needs to place the application's name in a notification or + | any other location as required by the application or its packages. + */ + 'name' => 'Pterodactyl', /* |-------------------------------------------------------------------------- @@ -99,7 +110,9 @@ return [ | */ - 'log' => 'daily', + 'log' => env('APP_LOG', 'daily'), + + 'log_level' => env('APP_LOG_LEVEL', 'debug'), /* |-------------------------------------------------------------------------- @@ -114,8 +127,6 @@ return [ 'providers' => [ - Dingo\Api\Provider\LaravelServiceProvider::class, - /* * Laravel Framework Service Providers... */ @@ -137,11 +148,15 @@ return [ Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, + /* + * Package Service Providers... + */ + Laravel\Tinker\TinkerServiceProvider::class, + /* * Application Service Providers... */ @@ -149,6 +164,8 @@ return [ Pterodactyl\Providers\AuthServiceProvider::class, Pterodactyl\Providers\EventServiceProvider::class, Pterodactyl\Providers\RouteServiceProvider::class, + Pterodactyl\Providers\MacroServiceProvider::class, + Pterodactyl\Providers\PhraseAppTranslationProvider::class, /* * Additional Dependencies @@ -158,6 +175,10 @@ return [ igaster\laravelTheme\themeServiceProvider::class, Prologue\Alerts\AlertsServiceProvider::class, Krucas\Settings\Providers\SettingsServiceProvider::class, + Fideloper\Proxy\TrustedProxyServiceProvider::class, + Laracasts\Utilities\JavaScript\JavaScriptServiceProvider::class, + Lord\Laroute\LarouteServiceProvider::class, + Spatie\Fractal\FractalServiceProvider::class, ], @@ -188,16 +209,16 @@ return [ 'Crypt' => Illuminate\Support\Facades\Crypt::class, 'DB' => Illuminate\Support\Facades\DB::class, 'Debugbar' => Barryvdh\Debugbar\Facade::class, - 'Dingo' => Dingo\Api\Facade\API::class, - 'DingoRoute'=> Dingo\Api\Facade\Route::class, 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'File' => Illuminate\Support\Facades\File::class, + 'Fractal' => Spatie\Fractal\FractalFacade::class, 'Gate' => Illuminate\Support\Facades\Gate::class, 'Google2FA' => PragmaRX\Google2FA\Vendor\Laravel\Facade::class, 'Hash' => Illuminate\Support\Facades\Hash::class, 'Input' => Illuminate\Support\Facades\Input::class, 'Inspiring' => Illuminate\Foundation\Inspiring::class, + 'Javascript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, @@ -217,6 +238,7 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Uuid' => Webpatser\Uuid\Uuid::class, 'Validator' => Illuminate\Support\Facades\Validator::class, + 'Version' => Pterodactyl\Facades\Version::class, 'View' => Illuminate\Support\Facades\View::class, ], diff --git a/config/auth.php b/config/auth.php index c8c53e14e..6f7fc83c2 100644 --- a/config/auth.php +++ b/config/auth.php @@ -69,11 +69,6 @@ return [ 'driver' => 'eloquent', 'model' => Pterodactyl\Models\User::class, ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], ], /* @@ -98,7 +93,6 @@ return [ 'passwords' => [ 'users' => [ 'provider' => 'users', - 'email' => 'emails.password', 'table' => 'password_resets', 'expire' => 60, ], diff --git a/config/broadcasting.php b/config/broadcasting.php index 36f9b3c14..85e045124 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -11,9 +11,10 @@ return [ | framework when an event needs to be broadcast. You may set this to | any of the connections defined in the "connections" array below. | + | Supported: "pusher", "redis", "log", "null" + | */ - - 'default' => env('BROADCAST_DRIVER', 'pusher'), + 'default' => env('BROADCAST_DRIVER', 'null'), /* |-------------------------------------------------------------------------- @@ -44,6 +45,10 @@ return [ 'driver' => 'log', ], + 'null' => [ + 'driver' => 'null', + ], + ], ]; diff --git a/config/cache.php b/config/cache.php index 379135b0e..7bd9dd70e 100644 --- a/config/cache.php +++ b/config/cache.php @@ -38,20 +38,29 @@ return [ 'database' => [ 'driver' => 'database', - 'table' => 'cache', + 'table' => 'cache', 'connection' => null, ], - 'file' => [ 'driver' => 'file', - 'path' => storage_path('framework/cache'), + 'path' => storage_path('framework/cache/data'), ], 'memcached' => [ - 'driver' => 'memcached', + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], 'servers' => [ [ - 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100, + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, ], ], ], diff --git a/config/database.php b/config/database.php index f6cf86b4c..58324a0b5 100644 --- a/config/database.php +++ b/config/database.php @@ -2,19 +2,6 @@ return [ - /* - |-------------------------------------------------------------------------- - | PDO Fetch Style - |-------------------------------------------------------------------------- - | - | By default, database results will be returned as instances of the PHP - | stdClass object; however, you may desire to retrieve records in an - | array format for simplicity. Here you can tweak the fetch style. - | - */ - - 'fetch' => PDO::FETCH_CLASS, - /* |-------------------------------------------------------------------------- | Default Database Connection Name @@ -45,16 +32,10 @@ return [ */ 'connections' => [ - - 'sqlite' => [ - 'driver' => 'sqlite', - 'database' => storage_path('database.sqlite'), - 'prefix' => '', - ], - 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), @@ -63,28 +44,6 @@ return [ 'prefix' => '', 'strict' => false, ], - - 'pgsql' => [ - 'driver' => 'pgsql', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'schema' => 'public', - ], - - 'sqlsrv' => [ - 'driver' => 'sqlsrv', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - ], - ], /* @@ -112,15 +71,13 @@ return [ */ 'redis' => [ - - 'cluster' => false, - + 'client' => 'predis', 'default' => [ - 'host' => '127.0.0.1', - 'port' => 6379, + 'host' => env('REDIS_HOST', 'localhost'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), 'database' => 0, ], - ], ]; diff --git a/config/debugbar.php b/config/debugbar.php index bae876c70..05e78c34c 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -1,6 +1,6 @@ array( + 'storage' => [ 'enabled' => true, - 'driver' => 'file', // redis, file, pdo + 'driver' => env('DEBUGBAR_DRIVER', 'file'), // redis, file, pdo 'path' => storage_path() . '/debugbar', // For file driver 'connection' => null, // Leave null for default connection (Redis/PDO) - ), + ], /* |-------------------------------------------------------------------------- @@ -81,7 +81,7 @@ return array( | */ - 'collectors' => array( + 'collectors' => [ 'phpinfo' => true, // Php version 'messages' => true, // Messages 'time' => true, // Time Datalogger @@ -92,7 +92,7 @@ return array( 'views' => true, // Views with their data 'route' => true, // Current route information 'laravel' => false, // Laravel version and environment - 'events' => false, // All events fired + 'events' => true, // All events fired 'default_request' => false, // Regular or special Symfony request logger 'symfony_request' => true, // Only one can be enabled.. 'mail' => true, // Catch mail messages @@ -102,7 +102,7 @@ return array( 'auth' => false, // Display Laravel authentication status 'gate' => false, // Display Laravel Gate checks 'session' => true, // Display session data - ), + ], /* |-------------------------------------------------------------------------- @@ -113,33 +113,33 @@ return array( | */ - 'options' => array( - 'auth' => array( + 'options' => [ + 'auth' => [ 'show_name' => false, // Also show the users name/email in the debugbar - ), - 'db' => array( + ], + 'db' => [ 'with_params' => true, // Render SQL with the parameters substituted - 'timeline' => false, // Add the queries to the timeline - 'backtrace' => false, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files. - 'explain' => array( // EXPERIMENTAL: Show EXPLAIN output on queries + 'timeline' => true, // Add the queries to the timeline + 'backtrace' => true, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files. + 'explain' => [ // EXPERIMENTAL: Show EXPLAIN output on queries 'enabled' => false, - 'types' => array('SELECT'), // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+ - ), - 'hints' => true, // Show hints for common mistakes - ), - 'mail' => array( - 'full_log' => false - ), - 'views' => array( + 'types' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+ + ], + 'hints' => false, // Show hints for common mistakes + ], + 'mail' => [ + 'full_log' => false, + ], + 'views' => [ 'data' => false, //Note: Can slow down the application, because the data can be quite large.. - ), - 'route' => array( - 'label' => true // show complete route on bar - ), - 'logs' => array( - 'file' => null - ), - ), + ], + 'route' => [ + 'label' => true, // show complete route on bar + ], + 'logs' => [ + 'file' => null, + ], + ], /* |-------------------------------------------------------------------------- @@ -166,4 +166,4 @@ return array( */ 'route_prefix' => '_debugbar', -); +]; diff --git a/config/filesystems.php b/config/filesystems.php index 3fffcf0a2..e726fda0c 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -39,45 +39,29 @@ return [ | may even configure multiple disks of the same driver. Defaults have | been setup for each driver as an example of the required options. | + | Supported Drivers: "local", "ftp", "s3", "rackspace" + | */ - 'disks' => [ 'local' => [ 'driver' => 'local', - 'root' => storage_path('app'), + 'root' => storage_path('app'), ], - 'ftp' => [ - 'driver' => 'ftp', - 'host' => 'ftp.example.com', - 'username' => 'your-username', - 'password' => 'your-password', - - // Optional FTP Settings... - // 'port' => 21, - // 'root' => '', - // 'passive' => true, - // 'ssl' => true, - // 'timeout' => 30, + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL') . '/storage', + 'visibility' => 'public', ], 's3' => [ 'driver' => 's3', - 'key' => 'your-key', - 'secret' => 'your-secret', - 'region' => 'your-region', - 'bucket' => 'your-bucket', - ], - - 'rackspace' => [ - 'driver' => 'rackspace', - 'username' => 'your-username', - 'key' => 'your-key', - 'container' => 'your-container', - 'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/', - 'region' => 'IAD', - 'url_type' => 'publicURL', + 'key' => env('AWS_KEY'), + 'secret' => env('AWS_SECRET'), + 'region' => env('AWS_REGION'), + 'bucket' => env('AWS_BUCKET'), ], ], diff --git a/config/javascript.php b/config/javascript.php new file mode 100644 index 000000000..1aeb222e0 --- /dev/null +++ b/config/javascript.php @@ -0,0 +1,32 @@ + [ + 'layouts.scripts', + ], + + /* + |-------------------------------------------------------------------------- + | JavaScript Namespace + |-------------------------------------------------------------------------- + | + | By default, we'll add variables to the global window object. However, + | it's recommended that you change this to some namespace - anything. + | That way, you can access vars, like "SomeNamespace.someVariable." + | + */ + 'js_namespace' => 'Pterodactyl', + +]; diff --git a/config/laravel-fractal.php b/config/laravel-fractal.php new file mode 100644 index 000000000..32ced203e --- /dev/null +++ b/config/laravel-fractal.php @@ -0,0 +1,18 @@ + League\Fractal\Serializer\JsonApiSerializer::class, + +]; diff --git a/config/laroute.php b/config/laroute.php new file mode 100644 index 000000000..7b332c40a --- /dev/null +++ b/config/laroute.php @@ -0,0 +1,58 @@ + 'public/js', + + /* + * The destination filename for the javascript file. + */ + 'filename' => 'laroute', + + /* + * The namespace for the helper functions. By default this will bind them to + * `window.laroute`. + */ + 'namespace' => 'Router', + + /* + * Generate absolute URLs + * + * Set the Application URL in config/app.php + */ + 'absolute' => false, + + /* + * The Filter Method + * + * 'all' => All routes except "'laroute' => false" + * 'only' => Only "'laroute' => true" routes + * 'force' => All routes, ignored "laroute" route parameter + */ + 'filter' => 'all', + + /* + * Controller Namespace + * + * Set here your controller namespace (see RouteServiceProvider -> $namespace) for cleaner action calls + * e.g. 'App\Http\Controllers' + */ + 'action_namespace' => '', + + /* + * The path to the template `laroute.js` file. This is the file that contains + * the ported helper Laravel url/route functions and the route data to go + * with them. + */ + 'template' => 'vendor/lord/laroute/src/templates/laroute.js', + + /* + * Appends a prefix to URLs. By default the prefix is an empty string. + * + */ + 'prefix' => '', + +]; diff --git a/config/mail.php b/config/mail.php index 07ea9c147..146e1b11c 100644 --- a/config/mail.php +++ b/config/mail.php @@ -11,7 +11,8 @@ return [ | sending of e-mail. You may specify which one you're using throughout | your application here. By default, Laravel is setup for SMTP mail. | - | Supported: "smtp", "mail", "sendmail", "mailgun", "mandrill", "ses", "log" + | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses", + | "sparkpost", "log", "array" | */ @@ -54,7 +55,10 @@ return [ | */ - 'from' => ['address' => env('MAIL_FROM'), 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel')], + 'from' => [ + 'address' => env('MAIL_FROM'), + 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel'), + ], /* |-------------------------------------------------------------------------- @@ -121,4 +125,22 @@ return [ 'pretend' => false, + /* + |-------------------------------------------------------------------------- + | Markdown Mail Settings + |-------------------------------------------------------------------------- + | + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! + | + */ + 'markdown' => [ + 'theme' => 'default', + + 'paths' => [ + resource_path('views/vendor/mail'), + ], + ], + ]; diff --git a/config/pterodactyl.php b/config/pterodactyl.php new file mode 100644 index 000000000..bd10183c6 --- /dev/null +++ b/config/pterodactyl.php @@ -0,0 +1,147 @@ + [ + 'core' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3', + 'author' => env('SERVICE_AUTHOR'), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication + |-------------------------------------------------------------------------- + | + | Should login success and failure events trigger an email to the user? + */ + 'auth' => [ + 'notifications' => env('LOGIN_NOTIFICATIONS', false), + ], + + /* + |-------------------------------------------------------------------------- + | Pagination + |-------------------------------------------------------------------------- + | + | Certain pagination result counts can be configured here and will take + | effect globally. + */ + 'paginate' => [ + 'frontend' => [ + 'servers' => env('APP_PAGINATE_FRONT_SERVERS', 15), + ], + 'api' => [ + 'nodes' => env('APP_PAGINATE_API_NODES', 25), + 'servers' => env('APP_PAGINATE_API_SERVERS', 25), + 'users' => env('APP_PAGINATE_API_USERS', 25), + ], + ], + + /* + |-------------------------------------------------------------------------- + | API Options + |-------------------------------------------------------------------------- + | + | Configuration options for the API. + */ + 'api' => [ + 'include_on_list' => env('API_INCLUDE_ON_LIST', false), + ], + + /* + |-------------------------------------------------------------------------- + | Guzzle Connections + |-------------------------------------------------------------------------- + | + | Configure the timeout to be used for Guzzle connections here. + */ + 'guzzle' => [ + 'timeout' => env('GUZZLE_TIMEOUT', 5), + 'connect_timeout' => env('GUZZLE_CONNECT_TIMEOUT', 3), + ], + + /* + |-------------------------------------------------------------------------- + | Queue Names + |-------------------------------------------------------------------------- + | + | Configure the names of queues to be used in the database. + */ + 'queues' => [ + 'low' => env('QUEUE_LOW', 'low'), + 'standard' => env('QUEUE_STANDARD', 'standard'), + 'high' => env('QUEUE_HIGH', 'high'), + ], + + /* + |-------------------------------------------------------------------------- + | Console Configuration + |-------------------------------------------------------------------------- + | + | Configure the speed at which data is rendered to the console. + */ + 'console' => [ + 'count' => env('CONSOLE_PUSH_COUNT', 10), + 'frequency' => env('CONSOLE_PUSH_FREQ', 200), + ], + + /* + |-------------------------------------------------------------------------- + | Task Timers + |-------------------------------------------------------------------------- + | + | The amount of time in minutes before performing certain actions on the system. + */ + 'tasks' => [ + 'clear_log' => env('PTERODACTYL_CLEAR_TASKLOG', 720), + 'delete_server' => env('PTERODACTYL_DELETE_MINUTES', 10), + ], + + /* + |-------------------------------------------------------------------------- + | CDN + |-------------------------------------------------------------------------- + | + | Information for the panel to use when contacting the CDN to confirm + | if panel is up to date. + */ + 'cdn' => [ + 'cache' => 60, + 'url' => 'https://cdn.pterodactyl.io/releases/latest.json', + ], + + /* + |-------------------------------------------------------------------------- + | Language Editor + |-------------------------------------------------------------------------- + | + | Set `PHRASE_IN_CONTEXT` to true to enable the PhaseApp in-context editor + | on this site which allows you to translate the panel, from the panel. + */ + 'lang' => [ + 'in_context' => env('PHRASE_IN_CONTEXT', false), + ], + + /* + |-------------------------------------------------------------------------- + | JSON Response Routes + |-------------------------------------------------------------------------- + | + | You should not edit this block. These routes are ajax based routes that + | expect content to be returned in JSON format. + */ + 'json_routes' => [ + 'api/*', + 'daemon/*', + 'remote/*', + ], +]; diff --git a/config/queue.php b/config/queue.php index 0a2181f77..a3d726f4a 100644 --- a/config/queue.php +++ b/config/queue.php @@ -7,12 +7,11 @@ return [ | Default Queue Driver |-------------------------------------------------------------------------- | - | The Laravel queue API supports a variety of back-ends via an unified + | Laravel's queue API supports an assortment of back-ends via a single | API, giving you convenient access to each back-end using the same | syntax for each one. Here you may set the default queue driver. | - | Supported: "null", "sync", "database", "beanstalkd", - | "sqs", "iron", "redis" + | Supported: "sync", "database", "beanstalkd", "sqs", "redis", "null" | */ @@ -39,7 +38,7 @@ return [ 'driver' => 'database', 'table' => 'jobs', 'queue' => env('QUEUE_STANDARD', 'standard'), - 'retry_after' => 60, + 'retry_after' => 90, ], 'sqs' => [ @@ -55,7 +54,7 @@ return [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('QUEUE_STANDARD', 'standard'), - 'retry_after' => 60, + 'retry_after' => 90, ], ], @@ -72,7 +71,8 @@ return [ */ 'failed' => [ - 'database' => 'mysql', 'table' => 'failed_jobs', + 'database' => 'mysql', + 'table' => 'failed_jobs', ], ]; diff --git a/config/recaptcha.php b/config/recaptcha.php new file mode 100644 index 000000000..646c9931a --- /dev/null +++ b/config/recaptcha.php @@ -0,0 +1,31 @@ + env('RECAPTCHA_ENABLED', true), + + /* + * API endpoint for recaptcha checks. You should not edit this. + */ + 'domain' => 'https://www.google.com/recaptcha/api/siteverify', + + /* + * Use a custom secret key, we use our public one by default + */ + 'secret_key' => env('RECAPTCHA_SECRET_KEY', '6LekAxoUAAAAAPW-PxNWaCLH76WkClMLSa2jImwD'), + + /* + * Use a custom website key, we use our public one by default + */ + 'website_key' => env('RECAPTCHA_WEBSITE_KEY', '6LekAxoUAAAAADjWZJ4ufcDRZBBiH9vfHawqRbup'), + + /* + * Domain verification is enabled by default and compares the domain used when solving the captcha + * as public keys can't have domain verification on google's side enabled (obviously). + */ + 'verify_domain' => true, + +]; diff --git a/config/services.php b/config/services.php index 0121c0b24..6ea80674c 100644 --- a/config/services.php +++ b/config/services.php @@ -29,10 +29,8 @@ return [ 'region' => 'us-east-1', ], - 'stripe' => [ - 'model' => Pterodactyl\Models\User::class, - 'key' => env('STRIPE_KEY'), - 'secret' => env('STRIPE_SECRET'), + 'sparkpost' => [ + 'secret' => env('SPARKPOST_SECRET'), ], ]; diff --git a/config/session.php b/config/session.php index 59ad9182f..0c1b3bb8e 100644 --- a/config/session.php +++ b/config/session.php @@ -16,7 +16,7 @@ return [ | */ - 'driver' => env('SESSION_DRIVER', 'file'), + 'driver' => env('SESSION_DRIVER', 'database'), /* |-------------------------------------------------------------------------- @@ -29,9 +29,9 @@ return [ | */ - 'lifetime' => 30, + 'lifetime' => 10080, - 'expire_on_close' => true, + 'expire_on_close' => false, /* |-------------------------------------------------------------------------- @@ -44,7 +44,7 @@ return [ | */ - 'encrypt' => false, + 'encrypt' => true, /* |-------------------------------------------------------------------------- @@ -85,6 +85,19 @@ return [ 'table' => 'sessions', + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using the "apc" or "memcached" session drivers, you may specify a + | cache store that should be used for these sessions. This value must + | correspond with one of the application's configured cache stores. + | + */ + + 'store' => null, + /* |-------------------------------------------------------------------------- | Session Sweeping Lottery @@ -109,7 +122,7 @@ return [ | */ - 'cookie' => 'laravel_session', + 'cookie' => 'pterodactyl_session', /* |-------------------------------------------------------------------------- @@ -135,7 +148,7 @@ return [ | */ - 'domain' => null, + 'domain' => env('SESSION_DOMAIN', null), /* |-------------------------------------------------------------------------- @@ -148,6 +161,19 @@ return [ | */ - 'secure' => false, + 'secure' => env('SESSION_SECURE_COOKIE', false), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. You are free to modify this option if needed. + | + */ + + 'http_only' => true, ]; diff --git a/config/themes.php b/config/themes.php index 870529767..f90a29f68 100644 --- a/config/themes.php +++ b/config/themes.php @@ -4,13 +4,13 @@ return [ 'enabled' => true, 'themes_path' => realpath(base_path('resources/themes')), 'asset_not_found' => 'LOG_ERROR', - 'active' => ENV('APP_THEME', 'default'), + 'active' => env('APP_THEME', 'pterodactyl'), 'themes' => [ - 'default' => [ + 'pterodactyl' => [ 'extends' => null, - 'views-path' => 'default', - 'asset-path' => 'themes/default', + 'views-path' => 'pterodactyl', + 'asset-path' => 'themes/pterodactyl', ], ], ]; diff --git a/config/trustedproxy.php b/config/trustedproxy.php new file mode 100644 index 000000000..a06211497 --- /dev/null +++ b/config/trustedproxy.php @@ -0,0 +1,60 @@ +getClientIp() + * always gets the originating client IP, no matter + * how many proxies that client's request has + * subsequently passed through. + */ + 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), + + /* + * Or, to trust all proxies that connect + * directly to your server, uncomment this: + */ + // 'proxies' => '*', + + /* + * Or, to trust ALL proxies, including those that + * are in a chain of fowarding, uncomment this: + */ + // 'proxies' => '**', + + /* + * Default Header Names + * + * Change these if the proxy does + * not send the default header names. + * + * Note that headers such as X-Forwarded-For + * are transformed to HTTP_X_FORWARDED_FOR format. + * + * The following are Symfony defaults, found in + * \Symfony\Component\HttpFoundation\Request::$trustedHeaders + */ + 'headers' => [ + \Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + \Illuminate\Http\Request::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + \Illuminate\Http\Request::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + \Illuminate\Http\Request::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ], +]; diff --git a/config/view.php b/config/view.php index e193ab61d..2acfd9cc9 100644 --- a/config/view.php +++ b/config/view.php @@ -14,7 +14,7 @@ return [ */ 'paths' => [ - realpath(base_path('resources/views')), + resource_path('views'), ], /* diff --git a/database/migrations/2016_01_23_195641_add_allocations_table.php b/database/migrations/2016_01_23_195641_add_allocations_table.php index 2edec4485..7384b7de2 100644 --- a/database/migrations/2016_01_23_195641_add_allocations_table.php +++ b/database/migrations/2016_01_23_195641_add_allocations_table.php @@ -29,6 +29,6 @@ class AddAllocationsTable extends Migration */ public function down() { - Schema::dropIfExsits('allocations'); + Schema::dropIfExists('allocations'); } } diff --git a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php index c1ba41b48..63eaf53a6 100644 --- a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php +++ b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php @@ -28,6 +28,6 @@ class CreateFailedJobsTable extends Migration */ public function down() { - Schema::drop('failed_jobs'); + Schema::dropIfExists('failed_jobs'); } } diff --git a/database/migrations/2016_01_23_200440_create_jobs_table.php b/database/migrations/2016_01_23_200440_create_jobs_table.php index 81b2d29f2..01d3a9e65 100644 --- a/database/migrations/2016_01_23_200440_create_jobs_table.php +++ b/database/migrations/2016_01_23_200440_create_jobs_table.php @@ -32,6 +32,6 @@ class CreateJobsTable extends Migration */ public function down() { - Schema::drop('jobs'); + Schema::dropIfExists('jobs'); } } diff --git a/database/migrations/2016_01_23_201649_add_server_variables.php b/database/migrations/2016_01_23_201649_add_server_variables.php index bfbb4b99d..229b33c60 100644 --- a/database/migrations/2016_01_23_201649_add_server_variables.php +++ b/database/migrations/2016_01_23_201649_add_server_variables.php @@ -28,8 +28,6 @@ class AddServerVariables extends Migration */ public function down() { - Schema::table('server_variables', function (Blueprint $table) { - // - }); + Schema::dropIfExists('server_variables'); } } diff --git a/database/migrations/2016_01_23_202544_add_service_options.php b/database/migrations/2016_01_23_202544_add_service_options.php index 1a05caec8..172f1eb7b 100644 --- a/database/migrations/2016_01_23_202544_add_service_options.php +++ b/database/migrations/2016_01_23_202544_add_service_options.php @@ -30,6 +30,6 @@ class AddServiceOptions extends Migration */ public function down() { - Schema::dropIfExsits('service_options'); + Schema::dropIfExists('service_options'); } } diff --git a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php index 65a0b3d9c..9065947d6 100644 --- a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php +++ b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php @@ -1,6 +1,5 @@ wrapTable('tasks'); - DB::statement('ALTER TABLE '.$table.' CHANGE `last_run` `last_run` TIMESTAMP NULL;'); + DB::statement('ALTER TABLE ' . $table . ' CHANGE `last_run` `last_run` TIMESTAMP NULL;'); } /** @@ -24,6 +23,6 @@ class AddNullableFieldLastrun extends Migration public function down() { $table = DB::getQueryGrammar()->wrapTable('tasks'); - DB::statement('ALTER TABLE '.$table.' CHANGE `last_run` `last_run` TIMESTAMP;'); + DB::statement('ALTER TABLE ' . $table . ' CHANGE `last_run` `last_run` TIMESTAMP;'); } } diff --git a/database/migrations/2016_08_30_212718_add_ip_alias.php b/database/migrations/2016_08_30_212718_add_ip_alias.php index 4726e5068..e75930edd 100644 --- a/database/migrations/2016_08_30_212718_add_ip_alias.php +++ b/database/migrations/2016_08_30_212718_add_ip_alias.php @@ -17,12 +17,12 @@ class AddIpAlias extends Migration }); $allocations = DB::select('SELECT id, ip FROM allocations'); - foreach($allocations as $allocation) { + foreach ($allocations as $allocation) { DB::update( 'UPDATE allocations SET ip_alias = :ip WHERE id = :id', [ 'ip' => $allocation->ip, - 'id' => $allocation->id + 'id' => $allocation->id, ] ); } diff --git a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php index e49d335d5..b77ccbea6 100644 --- a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php +++ b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php @@ -18,13 +18,13 @@ class ModifyIpStorageMethod extends Migration // Parse All Servers $servers = DB::select('SELECT id, ip, port, node FROM servers'); - foreach($servers as $server) { + foreach ($servers as $server) { $allocation = DB::select( 'SELECT id FROM allocations WHERE ip = :ip AND port = :port AND node = :node', [ 'ip' => $server->ip, 'port' => $server->port, - 'node' => $server->node + 'node' => $server->node, ] ); @@ -33,7 +33,7 @@ class ModifyIpStorageMethod extends Migration 'UPDATE servers SET allocation = :alocid WHERE id = :id', [ 'alocid' => $allocation[0]->id, - 'id' => $server->id + 'id' => $server->id, ] ); } @@ -44,7 +44,6 @@ class ModifyIpStorageMethod extends Migration $table->dropColumn('ip'); $table->dropColumn('port'); }); - } /** @@ -54,7 +53,6 @@ class ModifyIpStorageMethod extends Migration */ public function down() { - Schema::table('servers', function (Blueprint $table) { $table->text('ip')->after('allocation'); $table->integer('port')->unsigned()->after('ip'); @@ -62,8 +60,8 @@ class ModifyIpStorageMethod extends Migration // Find the allocations and reset the servers... $servers = DB::select('SELECT id, allocation FROM servers'); - foreach($servers as $server) { - $allocation = DB::select('SELECT * FROM allocations WHERE id = :alocid', [ 'alocid' => $server->allocation ]); + foreach ($servers as $server) { + $allocation = DB::select('SELECT * FROM allocations WHERE id = :alocid', ['alocid' => $server->allocation]); if (isset($allocation[0])) { DB::update( @@ -71,7 +69,7 @@ class ModifyIpStorageMethod extends Migration [ 'ip' => $allocation[0]->ip, 'port' => $allocation[0]->port, - 'id' => $server->id + 'id' => $server->id, ] ); } diff --git a/database/migrations/2016_09_17_194246_add_docker_image_column.php b/database/migrations/2016_09_17_194246_add_docker_image_column.php index bea676a08..58e4b87a3 100644 --- a/database/migrations/2016_09_17_194246_add_docker_image_column.php +++ b/database/migrations/2016_09_17_194246_add_docker_image_column.php @@ -4,8 +4,6 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; -use Pterodactyl\Models\Server; - class AddDockerImageColumn extends Migration { /** diff --git a/database/migrations/2016_09_29_213518_rename_double_insurgency.php b/database/migrations/2016_09_29_213518_rename_double_insurgency.php index 6c0ea8070..4fecb8bdf 100644 --- a/database/migrations/2016_09_29_213518_rename_double_insurgency.php +++ b/database/migrations/2016_09_29_213518_rename_double_insurgency.php @@ -1,7 +1,5 @@ text('user_agent'); $table->ipAddress('request_ip'); $table->timestampsTz(); - }); } diff --git a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php index 5a91e0138..0a5316755 100644 --- a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php +++ b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php @@ -1,7 +1,5 @@ select('env_variable')->where('env_variable', 'BUNGE_VERSION')->update([ - 'env_variable' => 'BUNGEE_VERSION' + 'env_variable' => 'BUNGEE_VERSION', ]); } @@ -25,6 +23,5 @@ class UpdateMisnamedBungee extends Migration */ public function down() { - return; } } diff --git a/database/migrations/2016_10_23_201624_add_foreign_allocations.php b/database/migrations/2016_10_23_201624_add_foreign_allocations.php index 6b73df2d6..8ff9bdd2f 100644 --- a/database/migrations/2016_10_23_201624_add_foreign_allocations.php +++ b/database/migrations/2016_10_23_201624_add_foreign_allocations.php @@ -12,17 +12,17 @@ class AddForeignAllocations extends Migration * @return void */ public function up() - { - DB::statement('ALTER TABLE allocations + { + DB::statement('ALTER TABLE allocations MODIFY COLUMN assigned_to INT(10) UNSIGNED NULL, MODIFY COLUMN node INT(10) UNSIGNED NOT NULL '); - Schema::table('allocations', function (Blueprint $table) { - $table->foreign('assigned_to')->references('id')->on('servers'); - $table->foreign('node')->references('id')->on('nodes'); - }); - } + Schema::table('allocations', function (Blueprint $table) { + $table->foreign('assigned_to')->references('id')->on('servers'); + $table->foreign('node')->references('id')->on('nodes'); + }); + } /** * Reverse the migrations. diff --git a/database/migrations/2016_10_23_203522_add_foreign_permissions.php b/database/migrations/2016_10_23_203522_add_foreign_permissions.php index 8de3f3b8f..153ab27ce 100644 --- a/database/migrations/2016_10_23_203522_add_foreign_permissions.php +++ b/database/migrations/2016_10_23_203522_add_foreign_permissions.php @@ -34,5 +34,4 @@ class AddForeignPermissions extends Migration $table->dropIndex('permissions_server_id_foreign'); }); } - } diff --git a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php index 116434161..af78a161c 100644 --- a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php +++ b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php @@ -32,17 +32,13 @@ class AddForeignServerVariables extends Migration public function down() { Schema::table('server_variables', function (Blueprint $table) { - $table->dropForeign('server_variables_server_id_foreign'); - $table->dropForeign('server_variables_variable_id_foreign'); - - $table->dropIndex('server_variables_server_id_foreign'); - $table->dropIndex('server_variables_variable_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropForeign(['variable_id']); }); - DB::statement('ALTER TABLE allocations + DB::statement('ALTER TABLE server_variables MODIFY COLUMN server_id MEDIUMINT(8) UNSIGNED NULL, MODIFY COLUMN variable_id MEDIUMINT(8) UNSIGNED NOT NULL '); } - } diff --git a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php index 4f7f43ae2..291ca24e2 100644 --- a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php +++ b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php @@ -34,4 +34,4 @@ class AddForeignServiceVariables extends Migration DB::statement('ALTER TABLE service_variables MODIFY option_id MEDIUMINT(8) UNSIGNED NOT NULL'); } - } +} diff --git a/database/migrations/2016_10_23_204610_add_foreign_tasks.php b/database/migrations/2016_10_23_204610_add_foreign_tasks.php index 77b736201..3821caffc 100644 --- a/database/migrations/2016_10_23_204610_add_foreign_tasks.php +++ b/database/migrations/2016_10_23_204610_add_foreign_tasks.php @@ -26,11 +26,7 @@ class AddForeignTasks extends Migration public function down() { Schema::table('tasks', function (Blueprint $table) { - $table->dropForeign('tasks_server_foreign'); - $table->dropForeign('tasks_server_foreign'); - - $table->dropIndex('tasks_server_foreign'); - $table->dropIndex('tasks_server_foreign'); + $table->dropForeign(['server']); }); } } diff --git a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php index fa8d5dde6..5a2dd6da4 100644 --- a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php +++ b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php @@ -1,7 +1,5 @@ select('id')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('name', 'Source Engine')->first(); // No SRCDS Service, Skipping - if (!$service) { + if (! $service) { return; } @@ -33,7 +31,7 @@ class AddArkServiceOptionFixed extends Migration 'tag' => 'ark', 'docker_image' => 'quay.io/pterodactyl/srcds:ark', 'executable' => './ShooterGameServer', - 'startup' => 'TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}' + 'startup' => 'TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}', ]); DB::table('service_variables')->insert([ @@ -45,7 +43,7 @@ class AddArkServiceOptionFixed extends Migration 'user_viewable' => 1, 'user_editable' => 1, 'required' => 0, - 'regex' => '/^(\w\.*)$/' + 'regex' => '/^(\w\.*)$/', ]); DB::table('service_variables')->insert([ @@ -57,7 +55,7 @@ class AddArkServiceOptionFixed extends Migration 'user_viewable' => 1, 'user_editable' => 1, 'required' => 0, - 'regex' => '/^(\w\.*)$/' + 'regex' => '/^(\w\.*)$/', ]); DB::table('service_variables')->insert([ @@ -69,10 +67,9 @@ class AddArkServiceOptionFixed extends Migration 'user_viewable' => 1, 'user_editable' => 1, 'required' => 1, - 'regex' => '/^(\d{1,4})$/' + 'regex' => '/^(\d{1,4})$/', ]); }); - } /** diff --git a/database/migrations/2016_11_11_220649_add_pack_support.php b/database/migrations/2016_11_11_220649_add_pack_support.php new file mode 100644 index 000000000..7cb3eb10e --- /dev/null +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -0,0 +1,40 @@ +increments('id'); + $table->unsignedInteger('option'); + $table->char('uuid', 36)->unique(); + $table->string('name'); + $table->string('version'); + $table->text('description')->nullable(); + $table->boolean('selectable')->default(true); + $table->boolean('visible')->default(true); + $table->timestamps(); + + $table->foreign('option')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('service_packs'); + } +} diff --git a/database/migrations/2016_11_11_231731_set_service_name_unique.php b/database/migrations/2016_11_11_231731_set_service_name_unique.php new file mode 100644 index 000000000..4db76f8e8 --- /dev/null +++ b/database/migrations/2016_11_11_231731_set_service_name_unique.php @@ -0,0 +1,32 @@ +unique('name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropUnique('services_name_unique'); + }); + } +} diff --git a/database/migrations/2016_11_27_142519_add_pack_column.php b/database/migrations/2016_11_27_142519_add_pack_column.php new file mode 100644 index 000000000..4e3507c39 --- /dev/null +++ b/database/migrations/2016_11_27_142519_add_pack_column.php @@ -0,0 +1,35 @@ +unsignedInteger('pack')->nullable()->after('option'); + + $table->foreign('pack')->references('id')->on('service_packs'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['pack']); + $table->dropColumn('pack'); + }); + } +} diff --git a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php new file mode 100644 index 000000000..91ef1fbbd --- /dev/null +++ b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php @@ -0,0 +1,32 @@ +unsignedInteger('upload_size')->after('disk_overallocate')->default(100); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropColumn('upload_size'); + }); + } +} diff --git a/database/migrations/2016_12_02_185206_correct_service_variables.php b/database/migrations/2016_12_02_185206_correct_service_variables.php new file mode 100644 index 000000000..dd99c1223 --- /dev/null +++ b/database/migrations/2016_12_02_185206_correct_service_variables.php @@ -0,0 +1,77 @@ +where([ + ['name', 'Spigot'], + ['tag', 'spigot'], + ['startup', '-Xms128M -Xmx{{SERVER_MEMORY}}M -Djline.terminal=jline.UnsupportedTerminal -jar {{SERVER_JARFILE}}'], + ])->update([ + 'startup' => null, + ]); + + // Correct Spigot Version Checking + DB::table('service_variables')->where([ + ['name', 'Spigot Version'], + ['env_variable', 'DL_VERSION'], + ['default_value', 'latest'], + ['regex', '/^(latest|[a-zA-Z0-9_\.-]{5,6})$/'], + ])->update([ + 'regex' => '/^(latest|[a-zA-Z0-9_\.-]{3,7})$/', + ]); + + // Correct Vanilla Version Checking (as well as naming) + DB::table('service_variables')->where([ + ['name', 'Server Jar File'], + ['env_variable', 'VANILLA_VERSION'], + ['default_value', 'latest'], + ['regex', '/^(latest|[a-zA-Z0-9_\.-]{5,6})$/'], + ])->update([ + 'name' => 'Server Version', + 'regex' => '/^(latest|[a-zA-Z0-9_\.-]{3,7})$/', + ]); + + // Update Sponge Version Checking and Update Default Version + DB::table('service_variables')->where([ + ['name', 'Sponge Version'], + ['env_variable', 'SPONGE_VERSION'], + ['default_value', '1.8.9-4.2.0-BETA-351'], + ['regex', '/^(.*)$/'], + ])->update([ + 'default_value' => '1.10.2-5.1.0-BETA-359', + 'regex' => '/^([a-zA-Z0-9.\-_]+)$/', + ]); + + // Update Bungeecord Version Checking + DB::table('service_variables')->where([ + ['name', 'Bungeecord Version'], + ['env_variable', 'BUNGEE_VERSION'], + ['default_value', 'latest'], + ['regex', '/^(latest|[\d]{3,5})$/'], + ])->update([ + 'regex' => '/^(latest|[\d]{1,6})$/', + ]); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // do nothing + } +} diff --git a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php new file mode 100644 index 000000000..a03584ca0 --- /dev/null +++ b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php @@ -0,0 +1,40 @@ +where([ + ['name', 'Sponge (SpongeVanilla)'], + ['tag', 'spigot'], + ['docker_image', 'quay.io/pterodactyl/minecraft:sponge'], + ])->update([ + 'tag' => 'sponge', + ]); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::table('service_options')->where([ + ['name', 'Sponge (SpongeVanilla)'], + ['tag', 'sponge'], + ['docker_image', 'quay.io/pterodactyl/minecraft:sponge'], + ])->update([ + 'tag' => 'spigot', + ]); + } +} diff --git a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php new file mode 100644 index 000000000..905d28a46 --- /dev/null +++ b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php @@ -0,0 +1,35 @@ +increments('id'); + $table->char('token', 32); + $table->timestamp('expires_at'); + $table->integer('node')->unsigned(); + $table->foreign('node')->references('id')->on('nodes'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('node_configuration_tokens'); + } +} diff --git a/database/migrations/2017_01_12_135449_add_more_user_data.php b/database/migrations/2017_01_12_135449_add_more_user_data.php new file mode 100644 index 000000000..67bc3f59d --- /dev/null +++ b/database/migrations/2017_01_12_135449_add_more_user_data.php @@ -0,0 +1,50 @@ +string('name_first')->after('email')->nullable(); + $table->string('name_last')->after('name_first')->nullable(); + $table->string('username')->after('uuid'); + $table->boolean('gravatar')->after('totp_secret')->default(true); + }); + + DB::transaction(function () { + foreach (User::all() as &$user) { + $user->username = $user->email; + $user->save(); + } + }); + + Schema::table('users', function (Blueprint $table) { + $table->string('username')->unique()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('name_first'); + $table->dropColumn('name_last'); + $table->dropColumn('username'); + $table->dropColumn('gravatar'); + }); + } +} diff --git a/database/migrations/2017_02_02_175548_UpdateColumnNames.php b/database/migrations/2017_02_02_175548_UpdateColumnNames.php new file mode 100644 index 000000000..233780bfd --- /dev/null +++ b/database/migrations/2017_02_02_175548_UpdateColumnNames.php @@ -0,0 +1,78 @@ +dropForeign('servers_node_foreign'); + $table->dropForeign('servers_owner_foreign'); + $table->dropForeign('servers_allocation_foreign'); + $table->dropForeign('servers_service_foreign'); + $table->dropForeign('servers_option_foreign'); + $table->dropForeign('servers_pack_foreign'); + + $table->dropIndex('servers_node_foreign'); + $table->dropIndex('servers_owner_foreign'); + $table->dropIndex('servers_allocation_foreign'); + $table->dropIndex('servers_service_foreign'); + $table->dropIndex('servers_option_foreign'); + $table->dropIndex('servers_pack_foreign'); + + $table->renameColumn('node', 'node_id'); + $table->renameColumn('owner', 'owner_id'); + $table->renameColumn('allocation', 'allocation_id'); + $table->renameColumn('service', 'service_id'); + $table->renameColumn('option', 'option_id'); + $table->renameColumn('pack', 'pack_id'); + + $table->foreign('node_id')->references('id')->on('nodes'); + $table->foreign('owner_id')->references('id')->on('users'); + $table->foreign('allocation_id')->references('id')->on('allocations'); + $table->foreign('service_id')->references('id')->on('services'); + $table->foreign('option_id')->references('id')->on('service_options'); + + // Pack ID was forgotten until multiple releases later, therefore it is + // contained in '2017_03_18_204953_AddForeignKeyToPacks' + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['node_id']); + $table->dropForeign(['owner_id']); + $table->dropForeign(['allocation_id']); + $table->dropForeign(['service_id']); + $table->dropForeign(['option_id']); + + $table->renameColumn('node_id', 'node'); + $table->renameColumn('owner_id', 'owner'); + $table->renameColumn('allocation_id', 'allocation'); + $table->renameColumn('service_id', 'service'); + $table->renameColumn('option_id', 'option'); + $table->renameColumn('pack_id', 'pack'); + + $table->foreign('node')->references('id')->on('nodes'); + $table->foreign('owner')->references('id')->on('users'); + $table->foreign('allocation')->references('id')->on('allocations'); + $table->foreign('service')->references('id')->on('services'); + $table->foreign('option')->references('id')->on('service_options'); + $table->foreign('pack')->references('id')->on('service_packs'); + }); + } +} diff --git a/database/migrations/2017_02_03_140948_UpdateNodesTable.php b/database/migrations/2017_02_03_140948_UpdateNodesTable.php new file mode 100644 index 000000000..4408e612b --- /dev/null +++ b/database/migrations/2017_02_03_140948_UpdateNodesTable.php @@ -0,0 +1,40 @@ +dropForeign('nodes_location_foreign'); + $table->dropIndex('nodes_location_foreign'); + + $table->renameColumn('location', 'location_id'); + $table->foreign('location_id')->references('id')->on('locations'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropForeign('nodes_location_id_foreign'); + $table->dropIndex('nodes_location_id_foreign'); + + $table->renameColumn('location_id', 'location'); + $table->foreign('location')->references('id')->on('locations'); + }); + } +} diff --git a/database/migrations/2017_02_03_155554_RenameColumns.php b/database/migrations/2017_02_03_155554_RenameColumns.php new file mode 100644 index 000000000..519ccc826 --- /dev/null +++ b/database/migrations/2017_02_03_155554_RenameColumns.php @@ -0,0 +1,48 @@ +dropForeign('allocations_node_foreign'); + $table->dropForeign('allocations_assigned_to_foreign'); + $table->dropIndex('allocations_node_foreign'); + $table->dropIndex('allocations_assigned_to_foreign'); + + $table->renameColumn('node', 'node_id'); + $table->renameColumn('assigned_to', 'server_id'); + $table->foreign('node_id')->references('id')->on('nodes'); + $table->foreign('server_id')->references('id')->on('servers'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign('allocations_node_id_foreign'); + $table->dropForeign('allocations_server_id_foreign'); + $table->dropIndex('allocations_node_id_foreign'); + $table->dropIndex('allocations_server_id_foreign'); + + $table->renameColumn('node_id', 'node'); + $table->renameColumn('server_id', 'assigned_to'); + $table->foreign('node')->references('id')->on('nodes'); + $table->foreign('assigned_to')->references('id')->on('servers'); + }); + } +} diff --git a/database/migrations/2017_02_05_164123_AdjustColumnNames.php b/database/migrations/2017_02_05_164123_AdjustColumnNames.php new file mode 100644 index 000000000..ddb37b891 --- /dev/null +++ b/database/migrations/2017_02_05_164123_AdjustColumnNames.php @@ -0,0 +1,40 @@ +dropForeign('service_options_parent_service_foreign'); + $table->dropIndex('service_options_parent_service_foreign'); + + $table->renameColumn('parent_service', 'service_id'); + $table->foreign('service_id')->references('id')->on('services'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign('service_options_service_id_foreign'); + $table->dropIndex('service_options_service_id_foreign'); + + $table->renameColumn('service_id', 'parent_service'); + $table->foreign('parent_service')->references('id')->on('services'); + }); + } +} diff --git a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php new file mode 100644 index 000000000..5e57ffef3 --- /dev/null +++ b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php @@ -0,0 +1,40 @@ +dropForeign('service_packs_option_foreign'); + $table->dropIndex('service_packs_option_foreign'); + + $table->renameColumn('option', 'option_id'); + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_packs', function (Blueprint $table) { + $table->dropForeign('service_packs_option_id_foreign'); + $table->dropIndex('service_packs_option_id_foreign'); + + $table->renameColumn('option_id', 'option'); + $table->foreign('option')->references('id')->on('service_options'); + }); + } +} diff --git a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php new file mode 100644 index 000000000..7194bf075 --- /dev/null +++ b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php @@ -0,0 +1,75 @@ +unsignedInteger('subuser_id')->after('id'); + }); + + DB::transaction(function () { + foreach (Subuser::all() as &$subuser) { + Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->update([ + 'subuser_id' => $subuser->id, + ]); + } + }); + + Schema::table('permissions', function (Blueprint $table) { + $table->dropForeign('permissions_server_id_foreign'); + $table->dropIndex('permissions_server_id_foreign'); + $table->dropForeign('permissions_user_id_foreign'); + $table->dropIndex('permissions_user_id_foreign'); + + $table->dropColumn('server_id'); + $table->dropColumn('user_id'); + $table->dropColumn('created_at'); + $table->dropColumn('updated_at'); + $table->foreign('subuser_id')->references('id')->on('subusers'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('permissions', function (Blueprint $table) { + $table->unsignedInteger('server_id')->after('subuser_id'); + $table->unsignedInteger('user_id')->after('server_id'); + $table->timestamps(); + }); + + DB::transaction(function () { + foreach (Subuser::all() as &$subuser) { + Permission::where('subuser_id', $subuser->id)->update([ + 'user_id' => $subuser->user_id, + 'server_id' => $subuser->server_id, + ]); + } + }); + + Schema::table('permissions', function (Blueprint $table) { + $table->dropForeign('permissions_subuser_id_foreign'); + $table->dropIndex('permissions_subuser_id_foreign'); + $table->dropColumn('subuser_id'); + + $table->foreign('server_id')->references('id')->on('servers'); + $table->foreign('user_id')->references('id')->on('users'); + }); + } +} diff --git a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php new file mode 100644 index 000000000..358f9938d --- /dev/null +++ b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php @@ -0,0 +1,38 @@ +dropForeign('api_keys_user_foreign')->dropIndex('api_keys_user_foreign'); + + $table->renameColumn('user', 'user_id'); + $table->foreign('user_id')->references('id')->on('users'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('api_keys', function (Blueprint $table) { + $table->dropForeign('api_keys_user_id_foreign')->dropIndex('api_keys_user_id_foreign'); + + $table->renameColumn('user_id', 'user'); + $table->foreign('user')->references('id')->on('users'); + }); + } +} diff --git a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php new file mode 100644 index 000000000..5931518d6 --- /dev/null +++ b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php @@ -0,0 +1,40 @@ +dropForeign(['node']); + $table->dropColumn('expires_at'); + $table->renameColumn('node', 'node_id'); + + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('node_configuration_tokens', function (Blueprint $table) { + $table->dropForeign(['node_id']); + $table->renameColumn('node_id', 'node'); + $table->timestamp('expires_at')->after('token'); + + $table->foreign('node')->references('id')->on('nodes'); + }); + } +} diff --git a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php new file mode 100644 index 000000000..e4af2511a --- /dev/null +++ b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php @@ -0,0 +1,66 @@ +get() as $option) { + $option->servers->each(function ($s) use ($option) { + $prepend = $option->display_executable; + $prepend = ($prepend === './ShooterGameServer') ? './ShooterGame/Binaries/Linux/ShooterGameServer' : $prepend; + $prepend = ($prepend === 'TerrariaServer.exe') ? 'mono TerrariaServer.exe' : $prepend; + + $s->startup = $prepend . ' ' . $s->startup; + + $container = $s->container; + if (starts_with($container, 'quay.io/pterodactyl/minecraft')) { + $s->container = 'quay.io/pterodactyl/core:java'; + } elseif (starts_with($container, 'quay.io/pterodactyl/srcds')) { + $s->container = 'quay.io/pterodactyl/core:source'; + } elseif (starts_with($container, 'quay.io/pterodactyl/voice')) { + $s->container = 'quay.io/pterodactyl/core:glibc'; + } elseif (starts_with($container, 'quay.io/pterodactyl/terraria')) { + $s->container = 'quay.io/pterodactyl/core:mono'; + } + + $s->save(); + }); + } + + Schema::table('services', function (Blueprint $table) { + $table->renameColumn('file', 'folder'); + $table->dropColumn('executable'); + $table->text('description')->nullable()->change(); + $table->text('startup')->nullable()->change(); + }); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->string('executable')->after('folder'); + $table->renameColumn('folder', 'file'); + $table->text('description')->nullable(false)->change(); + $table->text('startup')->nullable(false)->change(); + }); + } +} diff --git a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php new file mode 100644 index 000000000..351327d3c --- /dev/null +++ b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php @@ -0,0 +1,48 @@ +dropColumn('executable'); + + $table->unsignedInteger('config_from')->nullable()->after('docker_image'); + $table->string('config_stop')->nullable()->after('docker_image'); + $table->text('config_logs')->nullable()->after('docker_image'); + $table->text('config_startup')->nullable()->after('docker_image'); + $table->text('config_files')->nullable()->after('docker_image'); + + $table->foreign('config_from')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign(['config_from']); + + $table->dropColumn('config_from'); + $table->dropColumn('config_stop'); + $table->dropColumn('config_logs'); + $table->dropColumn('config_startup'); + $table->dropColumn('config_files'); + + $table->string('executable')->after('docker_image')->nullable(); + }); + } +} diff --git a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php new file mode 100644 index 000000000..ee247ee96 --- /dev/null +++ b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php @@ -0,0 +1,66 @@ +. + * + * 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. + */ +use Pterodactyl\Models\Service; +use Pterodactyl\Models\ServiceOption; +use Illuminate\Database\Migrations\Migration; + +class MigrateToNewServiceSystem extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + DB::transaction(function () { + $service = Service::where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first(); + if (! $service) { + return; + } + + $options = ServiceOption::where('service_id', $service->id)->get(); + $options->each(function ($item) use ($options) { + if ($item->tag === 'srcds' && $item->name === 'Insurgency') { + $item->tag = 'insurgency'; + } elseif ($item->tag === 'srcds' && $item->name === 'Team Fortress 2') { + $item->tag = 'tf2'; + } elseif ($item->tag === 'srcds' && $item->name === 'Custom Source Engine Game') { + $item->tag = 'source'; + } + $item->save(); + }); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // Not doing reversals right now... + } +} diff --git a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php new file mode 100644 index 000000000..617c349fa --- /dev/null +++ b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php @@ -0,0 +1,52 @@ +renameColumn('regex', 'rules'); + }); + + DB::transaction(function () { + foreach (ServiceVariable::all() as $variable) { + $variable->rules = ($variable->required) ? 'required|regex:' . $variable->rules : 'regex:' . $variable->rules; + $variable->save(); + } + }); + + Schema::table('service_variables', function (Blueprint $table) { + $table->dropColumn('required'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_variables', function (Blueprint $table) { + $table->renameColumn('rules', 'regex'); + $table->boolean('required')->default(true)->before('regex'); + }); + + DB::transaction(function () { + foreach (ServiceVariable::all() as $variable) { + $variable->regex = str_replace(['required|regex:', 'regex:'], '', $variable->regex); + $variable->save(); + } + }); + } +} diff --git a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php new file mode 100644 index 000000000..bbd5fda42 --- /dev/null +++ b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php @@ -0,0 +1,119 @@ + + * + * 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. + */ +const rfr = require('rfr'); +const _ = require('lodash'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service; +EOF; + + private $default_mc = <<<'EOF' +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2017 Dane Everitt + * + * 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. + */ +const rfr = require('rfr'); +const _ = require('lodash'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core { + onConsole(data) { + // Hide the output spam from Bungeecord getting pinged. + if (_.endsWith(data, '<-> InitialHandler has connected')) return; + return super.onConsole(data); + } +} + +module.exports = Service; +EOF; + + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('services', function (Blueprint $table) { + $table->text('index_file')->after('startup'); + }); + + DB::transaction(function () { + Service::where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', '!=', 'minecraft')->update([ + 'index_file' => $this->default, + ]); + + Service::where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', 'minecraft')->update([ + 'index_file' => $this->default_mc, + ]); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('index_file'); + }); + } +} diff --git a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php new file mode 100644 index 000000000..910fb79df --- /dev/null +++ b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php @@ -0,0 +1,44 @@ +dropForeign(['option_id']); + }); + + Schema::rename('service_packs', 'packs'); + + Schema::table('packs', function (Blueprint $table) { + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('packs', function (Blueprint $table) { + $table->dropForeign(['option_id']); + }); + + Schema::rename('packs', 'service_packs'); + + Schema::table('service_packs', function (Blueprint $table) { + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } +} diff --git a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php new file mode 100644 index 000000000..4916cd028 --- /dev/null +++ b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php @@ -0,0 +1,32 @@ +boolean('locked')->default(false)->after('visible'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('packs', function (Blueprint $table) { + $table->dropColumn('locked'); + }); + } +} diff --git a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php new file mode 100644 index 000000000..50699a688 --- /dev/null +++ b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php @@ -0,0 +1,48 @@ +dropForeign(['linked_node']); + }); + + Schema::rename('database_servers', 'database_hosts'); + + Schema::table('database_hosts', function (Blueprint $table) { + $table->renameColumn('linked_node', 'node_id'); + + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('database_hosts', function (Blueprint $table) { + $table->dropForeign(['node_id']); + }); + + Schema::rename('database_hosts', 'database_servers'); + + Schema::table('database_servers', function (Blueprint $table) { + $table->renameColumn('node_id', 'linked_node'); + + $table->foreign('linked_node')->references('id')->on('nodes'); + }); + } +} diff --git a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php new file mode 100644 index 000000000..07192f898 --- /dev/null +++ b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php @@ -0,0 +1,40 @@ +dropForeign(['db_server']); + + $table->renameColumn('db_server', 'database_host_id'); + + $table->foreign('database_host_id')->references('id')->on('database_hosts'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('databases', function (Blueprint $table) { + $table->dropForeign(['database_host_id']); + + $table->renameColumn('database_host_id', 'db_server'); + + $table->foreign('db_server')->references('id')->on('database_hosts'); + }); + } +} diff --git a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php new file mode 100644 index 000000000..0271616a0 --- /dev/null +++ b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php @@ -0,0 +1,32 @@ +foreign('pack_id')->references('id')->on('packs'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['pack_id']); + }); + } +} diff --git a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php new file mode 100644 index 000000000..1e5ce0273 --- /dev/null +++ b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php @@ -0,0 +1,32 @@ +text('description')->after('name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('description'); + }); + } +} diff --git a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php new file mode 100644 index 000000000..ccd318654 --- /dev/null +++ b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php @@ -0,0 +1,32 @@ +dropColumn('deleted_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->timestamp('deleted_at')->nullable(); + }); + } +} diff --git a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php new file mode 100644 index 000000000..5264122b4 --- /dev/null +++ b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php @@ -0,0 +1,52 @@ +dropForeign(['server']); + + $table->renameColumn('server', 'server_id'); + $table->unsignedInteger('user_id')->nullable()->after('id'); + + $table->foreign('server_id')->references('id')->on('servers'); + $table->foreign('user_id')->references('id')->on('users'); + }); + + DB::transaction(function () { + foreach (Task::all() as $task) { + $task->user_id = $task->server->owner_id; + $task->save(); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('tasks', function (Blueprint $table) { + $table->dropForeign(['server_id']); + $table->dropForeign(['user_id']); + + $table->renameColumn('server_id', 'server'); + $table->dropColumn('user_id'); + + $table->foreign('server')->references('id')->on('servers'); + }); + } +} diff --git a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php new file mode 100644 index 000000000..610f18e5f --- /dev/null +++ b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php @@ -0,0 +1,38 @@ +text('script_install')->after('startup')->nullable(); + $table->boolean('script_is_privileged')->default(true)->after('startup'); + $table->string('script_entry')->default('ash')->after('startup'); + $table->string('script_container')->default('alpine:3.4')->after('startup'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropColumn('script_install'); + $table->dropColumn('script_is_privileged'); + $table->dropColumn('script_entry'); + $table->dropColumn('script_container'); + }); + } +} diff --git a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php new file mode 100644 index 000000000..07afdfeea --- /dev/null +++ b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php @@ -0,0 +1,32 @@ +boolean('skip_scripts')->default(false)->after('description'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('skip_scripts'); + }); + } +} diff --git a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php new file mode 100644 index 000000000..027d1964b --- /dev/null +++ b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php @@ -0,0 +1,35 @@ +unsignedInteger('copy_script_from')->nullable()->after('script_container'); + + $table->foreign('copy_script_from')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign(['copy_script_from']); + $table->dropColumn('copy_script_from'); + }); + } +} diff --git a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php new file mode 100644 index 000000000..f82d39258 --- /dev/null +++ b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php @@ -0,0 +1,32 @@ +boolean('behind_proxy')->after('scheme')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropColumn('behind_proxy'); + }); + } +} diff --git a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php new file mode 100644 index 000000000..90a7f7a6a --- /dev/null +++ b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php @@ -0,0 +1,34 @@ +increments('id'); + $table->char('token', 36)->unique(); + $table->char('server', 36); + $table->text('path'); + $table->timestamps(); + }); + } +} diff --git a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php new file mode 100644 index 000000000..369c867be --- /dev/null +++ b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php @@ -0,0 +1,37 @@ +increments('id'); + $table->char('token', 32); + $table->unsignedInteger('node_id'); + $table->timestamps(); + }); + + Schema::table('node_configuration_tokens', function (Blueprint $table) { + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +} diff --git a/database/seeds/MinecraftServiceTableSeeder.php b/database/seeds/MinecraftServiceTableSeeder.php index ff141abdb..c2ed986e3 100644 --- a/database/seeds/MinecraftServiceTableSeeder.php +++ b/database/seeds/MinecraftServiceTableSeeder.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,25 +22,67 @@ * SOFTWARE. */ use Illuminate\Database\Seeder; - -use Pterodactyl\Models; +use Pterodactyl\Models\Service; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Models\ServiceVariable; class MinecraftServiceTableSeeder extends Seeder { /** * The core service ID. * - * @var Models\Service + * @var \Pterodactyl\Models\Service */ protected $service; /** - * Stores all of the option objects + * Stores all of the option objects. * * @var array */ protected $option = []; + private $default_mc = <<<'EOF' +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2017 Dane Everitt + * + * 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. + */ +const rfr = require('rfr'); +const _ = require('lodash'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core { + onConsole(data) { + // Hide the output spam from Bungeecord getting pinged. + if (_.endsWith(data, '<-> InitialHandler has connected')) return; + return super.onConsole(data); + } +} + +module.exports = Service; +EOF; + /** * Run the database seeds. * @@ -55,56 +97,151 @@ class MinecraftServiceTableSeeder extends Seeder private function addCoreService() { - $this->service = Models\Service::create([ - 'author' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3', + $this->service = Service::updateOrCreate([ + 'author' => config('pterodactyl.service.core'), + 'folder' => 'minecraft', + ], [ 'name' => 'Minecraft', 'description' => 'Minecraft - the classic game from Mojang. With support for Vanilla MC, Spigot, and many others!', - 'file' => 'minecraft', - 'executable' => 'java', - 'startup' => '-Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}' + 'startup' => 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}', + 'index_file' => $this->default_mc, ]); } private function addCoreOptions() { - $this->option['vanilla'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $script = <<<'EOF' +#!/bin/ash +# Vanilla MC Installation Script +# +# Server Files: /mnt/server +apk update +apk add curl + +cd /mnt/server + +LATEST_VERSION=`curl -s https://s3.amazonaws.com/Minecraft.Download/versions/versions.json | grep -o "[[:digit:]]\.[0-9]*\.[0-9]" | head -n 1` + +if [ -z "$VANILLA_VERSION" ] || [ "$VANILLA_VERSION" == "latest" ]; then + DL_VERSION=$LATEST_VERSION +else + DL_VERSION=$VANILLA_VERSION +fi + +curl -o ${SERVER_JARFILE} https://s3.amazonaws.com/Minecraft.Download/versions/${DL_VERSION}/minecraft_server.${DL_VERSION}.jar +EOF; + + $this->option['vanilla'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'vanilla', + ], [ 'name' => 'Vanilla Minecraft', 'description' => 'Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.', - 'tag' => 'vanilla', - 'docker_image' => 'quay.io/pterodactyl/minecraft', - 'executable' => null, - 'startup' => null + 'docker_image' => 'quay.io/pterodactyl/core:java', + 'config_startup' => '{"done": ")! For help, type ", "userInteraction": [ "Go to eula.txt for more info."]}', + 'config_logs' => '{"custom": false, "location": "logs/latest.log"}', + 'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "enable-query": "true", "server-port": "{{server.build.default.port}}", "query.port": "{{server.build.default.port}}"}}}', + 'config_stop' => 'stop', + 'config_from' => null, + 'startup' => null, + 'script_install' => $script, ]); - $this->option['spigot'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $script = <<<'EOF' +#!/bin/ash +# Spigot Installation Script +# +# Server Files: /mnt/server + +## Only download if a path is provided, otherwise continue. +if [ ! -z "${DL_PATH}" ]; then + apk update + apk add curl + + cd /mnt/server + + MODIFIED_DOWNLOAD=`eval echo $(echo ${DL_PATH} | sed -e 's/{{/${/g' -e 's/}}/}/g')` + curl -sSL -o ${SERVER_JARFILE} ${MODIFIED_DOWNLOAD} +fi +EOF; + + $this->option['spigot'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'spigot', + ], [ 'name' => 'Spigot', 'description' => 'Spigot is the most widely-used modded Minecraft server software in the world. It powers many of the top Minecraft server networks around to ensure they can cope with their huge player base and ensure the satisfaction of their players. Spigot works by reducing and eliminating many causes of lag, as well as adding in handy features and settings that help make your job of server administration easier.', - 'tag' => 'spigot', - 'docker_image' => 'quay.io/pterodactyl/minecraft:spigot', - 'executable' => null, - 'startup' => '-Xms128M -Xmx{{SERVER_MEMORY}}M -Djline.terminal=jline.UnsupportedTerminal -jar {{SERVER_JARFILE}}' + 'docker_image' => 'quay.io/pterodactyl/core:java-glibc', + 'config_startup' => null, + 'config_files' => '{"spigot.yml":{"parser": "yaml", "find":{"settings.restart-on-crash": false}}}', + 'config_logs' => null, + 'config_stop' => null, + 'config_from' => $this->option['vanilla']->id, + 'startup' => null, + 'script_install' => $script, ]); - $this->option['sponge'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $script = <<<'EOF' +#!/bin/ash +# Sponge Installation Script +# +# Server Files: /mnt/server + +apk update +apk add curl + +cd /mnt/server + +curl -sSL "https://repo.spongepowered.org/maven/org/spongepowered/spongevanilla/${SPONGE_VERSION}/spongevanilla-${SPONGE_VERSION}.jar" -o ${SERVER_JARFILE} +EOF; + + $this->option['sponge'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'sponge', + ], [ 'name' => 'Sponge (SpongeVanilla)', 'description' => 'SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.', - 'tag' => 'spigot', - 'docker_image' => 'quay.io/pterodactyl/minecraft:sponge', - 'executable' => null, - 'startup' => null + 'docker_image' => 'quay.io/pterodactyl/core:java-glibc', + 'config_startup' => '{"userInteraction": [ "You need to agree to the EULA"]}', + 'config_files' => null, + 'config_logs' => null, + 'config_stop' => null, + 'config_from' => $this->option['vanilla']->id, + 'startup' => null, + 'script_install' => $script, ]); - $this->option['bungeecord'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $script = <<<'EOF' +#!/bin/ash +# Bungeecord Installation Script +# +# Server Files: /mnt/server +apk update +apk add curl + +cd /mnt/server + +if [ -z "${BUNGEE_VERSION}" ] || [ "${BUNGEE_VERSION}" == "latest" ]; then + BUNGEE_VERSION="lastStableBuild" +fi + +curl -o ${SERVER_JARFILE} https://ci.md-5.net/job/BungeeCord/${BUNGEE_VERSION}/artifact/bootstrap/target/BungeeCord.jar +EOF; + + $this->option['bungeecord'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'bungeecord', + ], [ 'name' => 'Bungeecord', 'description' => 'For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community\'s full potential.', - 'tag' => 'bungeecord', - 'docker_image' => 'quay.io/pterodactyl/minecraft:bungeecord', - 'executable' => null, - 'startup' => null + 'docker_image' => 'quay.io/pterodactyl/core:java', + 'config_startup' => '{"done": "Listening on ", "userInteraction": [ "Listening on /0.0.0.0:25577"]}', + 'config_files' => '{"config.yml":{"parser": "yaml", "find":{"listeners[0].query_enabled": true, "listeners[0].query_port": "{{server.build.default.port}}", "listeners[0].host": "0.0.0.0:{{server.build.default.port}}", "servers.*.address":{"127.0.0.1": "{{config.docker.interface}}", "localhost": "{{config.docker.interface}}"}}}}', + 'config_logs' => '{"custom": false, "location": "proxy.log.0"}', + 'config_stop' => 'end', + 'config_from' => null, + 'startup' => null, + 'script_install' => $script, ]); } @@ -118,121 +255,121 @@ class MinecraftServiceTableSeeder extends Seeder private function addVanillaVariables() { - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['vanilla']->id, + 'env_variable' => 'SERVER_JARFILE', + ], [ 'name' => 'Server Jar File', 'description' => 'The name of the server jarfile to run the server with.', - 'env_variable' => 'SERVER_JARFILE', 'default_value' => 'server.jar', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^([\w\d._-]+)(\.jar)$/' + 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['vanilla']->id, - 'name' => 'Server Jar File', - 'description' => 'The version of Minecraft Vanilla to install. Use "latest" to install the latest version.', 'env_variable' => 'VANILLA_VERSION', + ], [ + 'name' => 'Server Version', + 'description' => 'The version of Minecraft Vanilla to install. Use "latest" to install the latest version.', 'default_value' => 'latest', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^(latest|[a-zA-Z0-9_\.-]{5,6})$/' + 'rules' => 'required|string|between:3,7', ]); } private function addSpigotVariables() { - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['spigot']->id, + 'env_variable' => 'SERVER_JARFILE', + ], [ 'name' => 'Server Jar File', 'description' => 'The name of the server jarfile to run the server with.', - 'env_variable' => 'SERVER_JARFILE', 'default_value' => 'server.jar', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^([\w\d._-]+)(\.jar)$/' + 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['spigot']->id, + 'env_variable' => 'DL_VERSION', + ], [ 'name' => 'Spigot Version', 'description' => 'The version of Spigot to download (using the --rev tag). Use "latest" for latest.', - 'env_variable' => 'DL_VERSION', 'default_value' => 'latest', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^(latest|[a-zA-Z0-9_\.-]{5,6})$/' + 'rules' => 'required|string|between:3,7', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['spigot']->id, + 'env_variable' => 'DL_PATH', + ], [ 'name' => 'Download Path', 'description' => 'A URL to use to download Spigot rather than building it on the server. This is not user viewable. Use {{DL_VERSION}} in the URL to automatically insert the assigned version into the URL. If you do not enter a URL Spigot will build directly in the container (this will fail on low memory containers).', - 'env_variable' => 'DL_PATH', 'default_value' => '', 'user_viewable' => 0, 'user_editable' => 0, - 'required' => 0, - 'regex' => '/^(.*)$/' + 'rules' => 'string', ]); } private function addSpongeVariables() { - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['sponge']->id, + 'env_variable' => 'SPONGE_VERSION', + ], [ 'name' => 'Sponge Version', 'description' => 'The version of SpongeVanilla to download and use.', - 'env_variable' => 'SPONGE_VERSION', - 'default_value' => '1.8.9-4.2.0-BETA-351', + 'default_value' => '1.10.2-5.2.0-BETA-381', 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(.*)$/' + 'rules' => 'required|regex:/^([a-zA-Z0-9.\-_]+)$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['sponge']->id, + 'env_variable' => 'SERVER_JARFILE', + ], [ 'name' => 'Server Jar File', 'description' => 'The name of the Jarfile to use when running SpongeVanilla.', - 'env_variable' => 'SERVER_JARFILE', 'default_value' => 'server.jar', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^([\w\d._-]+)(\.jar)$/' + 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', ]); } private function addBungeecordVariables() { - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['bungeecord']->id, + 'env_variable' => 'BUNGEE_VERSION', + ], [ 'name' => 'Bungeecord Version', 'description' => 'The version of Bungeecord to download and use.', - 'env_variable' => 'BUNGEE_VERSION', 'default_value' => 'latest', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^(latest|[\d]{3,5})$/' + 'rules' => 'required|alpha_num|between:1,6', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['bungeecord']->id, + 'env_variable' => 'SERVER_JARFILE', + ], [ 'name' => 'Bungeecord Jar File', 'description' => 'The name of the Jarfile to use when running Bungeecord.', - 'env_variable' => 'SERVER_JARFILE', 'default_value' => 'bungeecord.jar', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^([\w\d._-]+)(\.jar)$/' + 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', ]); } } diff --git a/database/seeds/SourceServiceTableSeeder.php b/database/seeds/SourceServiceTableSeeder.php index 2c4b25d55..171aebecc 100644 --- a/database/seeds/SourceServiceTableSeeder.php +++ b/database/seeds/SourceServiceTableSeeder.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,8 +22,9 @@ * SOFTWARE. */ use Illuminate\Database\Seeder; - -use Pterodactyl\Models; +use Pterodactyl\Models\Service; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Models\ServiceVariable; class SourceServiceTableSeeder extends Seeder { @@ -35,7 +36,7 @@ class SourceServiceTableSeeder extends Seeder protected $service; /** - * Stores all of the option objects + * Stores all of the option objects. * * @var array */ @@ -55,56 +56,141 @@ class SourceServiceTableSeeder extends Seeder private function addCoreService() { - $this->service = Models\Service::create([ - 'author' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3', + $this->service = Service::updateOrCreate([ + 'author' => config('pterodactyl.service.core'), + 'folder' => 'srcds', + ], [ 'name' => 'Source Engine', 'description' => 'Includes support for most Source Dedicated Server games.', - 'file' => 'srcds', - 'executable' => './srcds_run', - 'startup' => '-game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} -strictportbind -norestart' + 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +ip 0.0.0.0 -strictportbind -norestart', + 'index_file' => Service::defaultIndexFile(), ]); } private function addCoreOptions() { - $this->option['insurgency'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, - 'name' => 'Insurgency', - 'description' => 'Take to the streets for intense close quarters combat, where a team\'s survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.', - 'tag' => 'srcds', - 'docker_image' => 'quay.io/pterodactyl/srcds', - 'executable' => null, - 'startup' => '-game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} -strictportbind -norestart' - ]); + $script = <<<'EOF' +#!/bin/bash +# SRCDS Base Installation Script +# +# Server Files: /mnt/server +apt -y update +apt -y --no-install-recommends install curl lib32gcc1 ca-certificates - $this->option['tf2'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, - 'name' => 'Team Fortress 2', - 'description' => 'Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.', - 'tag' => 'srcds', - 'docker_image' => 'quay.io/pterodactyl/srcds', - 'executable' => null, - 'startup' => '-game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} -strictportbind -norestart' - ]); +cd /tmp +curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - $this->option['ark'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, - 'name' => 'Ark: Survival Evolved', - 'description' => 'As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! — Gamepedia: ARK', - 'tag' => 'ark', - 'docker_image' => 'quay.io/pterodactyl/srcds:ark', - 'executable' => './ShooterGameServer', - 'startup' => 'TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}' - ]); +mkdir -p /mnt/server/steamcmd +tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd +cd /mnt/server/steamcmd - $this->option['custom'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, +# SteamCMD fails otherwise for some reason, even running as root. +# This is changed at the end of the install process anyways. +chown -R root:root /mnt + +export HOME=/mnt/server +./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update ${SRCDS_APPID} +quit + +mkdir -p /mnt/server/.steam/sdk32 +cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so +EOF; + + $this->option['source'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'source', + ], [ 'name' => 'Custom Source Engine Game', 'description' => 'This option allows modifying the startup arguments and other details to run a custo SRCDS based game on the panel.', - 'tag' => 'srcds', - 'docker_image' => 'quay.io/pterodactyl/srcds', - 'executable' => null, - 'startup' => null + 'docker_image' => 'quay.io/pterodactyl/core:source', + 'config_startup' => '{"done": "gameserver Steam ID", "userInteraction": []}', + 'config_files' => '{}', + 'config_logs' => '{"custom": true, "location": "logs/latest.log"}', + 'config_stop' => 'quit', + 'config_from' => null, + 'startup' => null, + 'script_install' => $script, + 'script_entry' => 'bash', + 'script_container' => 'ubuntu:16.04', + ]); + + $this->option['insurgency'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'insurgency', + ], [ + 'name' => 'Insurgency', + 'description' => 'Take to the streets for intense close quarters combat, where a team\'s survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.', + 'docker_image' => 'quay.io/pterodactyl/core:source', + 'config_startup' => null, + 'config_files' => null, + 'config_logs' => null, + 'config_stop' => null, + 'config_from' => $this->option['source']->id, + 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart', + 'copy_script_from' => $this->option['source']->id, + ]); + + $this->option['tf2'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'tf2', + ], [ + 'name' => 'Team Fortress 2', + 'description' => 'Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.', + 'docker_image' => 'quay.io/pterodactyl/core:source', + 'config_startup' => null, + 'config_files' => null, + 'config_logs' => null, + 'config_stop' => null, + 'config_from' => $this->option['source']->id, + 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart', + 'copy_script_from' => $this->option['source']->id, + ]); + + $script = <<<'EOF' +#!/bin/bash +# ARK: Installation Script +# +# Server Files: /mnt/server +apt -y update +apt -y --no-install-recommends install curl lib32gcc1 ca-certificates + +cd /tmp +curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz + +mkdir -p /mnt/server/steamcmd +mkdir -p /mnt/server/Engine/Binaries/ThirdParty/SteamCMD/Linux + +tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd +tar -xzvf steamcmd.tar.gz -C /mnt/server/Engine/Binaries/ThirdParty/SteamCMD/Linux + +cd /mnt/server/steamcmd + +# SteamCMD fails otherwise for some reason, even running as root. +# This is changed at the end of the install process anyways. +chown -R root:root /mnt + +export HOME=/mnt/server +./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update 376030 +quit + +mkdir -p /mnt/server/.steam/sdk32 +cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so +EOF; + + $this->option['ark'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'ark', + ], [ + 'name' => 'Ark: Survival Evolved', + 'description' => 'As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! — Gamepedia: ARK', + 'docker_image' => 'quay.io/pterodactyl/core:source', + 'config_startup' => '{"done": "Setting breakpad minidump AppID"}', + 'config_files' => null, + 'config_logs' => null, + 'config_stop' => '^C', + 'config_from' => $this->option['source']->id, + 'startup' => './ShooterGame/Binaries/Linux/ShooterGameServer TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}', + 'script_install' => $script, + 'script_entry' => 'bash', + 'script_container' => 'ubuntu:16.04', ]); } @@ -118,145 +204,145 @@ class SourceServiceTableSeeder extends Seeder private function addInsurgencyVariables() { - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['insurgency']->id, + 'env_variable' => 'SRCDS_APPID', + ], [ 'name' => 'Game ID', 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'env_variable' => 'SRCDS_APPID', 'default_value' => '17705', 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(17705)$/' + 'rules' => 'required|regex:/^(17705)$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['insurgency']->id, + 'env_variable' => 'SRCDS_GAME', + ], [ 'name' => 'Game Name', 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'env_variable' => 'SRCDS_GAME', 'default_value' => 'insurgency', 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(insurgency)$/' + 'rules' => 'required|regex:/^(insurgency)$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['insurgency']->id, + 'env_variable' => 'SRCDS_MAP', + ], [ 'name' => 'Default Map', 'description' => 'The default map to use when starting the server.', - 'env_variable' => 'SRCDS_MAP', 'default_value' => 'sinjar', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^(\w{1,20})$/' + 'rules' => 'required|regex:/^(\w{1,20})$/', ]); } private function addTF2Variables() { - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['tf2']->id, + 'env_variable' => 'SRCDS_APPID', + ], [ 'name' => 'Game ID', 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'env_variable' => 'SRCDS_APPID', 'default_value' => '232250', 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(232250)$/' + 'rules' => 'required|regex:/^(232250)$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['tf2']->id, + 'env_variable' => 'SRCDS_GAME', + ], [ 'name' => 'Game Name', 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'env_variable' => 'SRCDS_GAME', 'default_value' => 'tf', 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(tf)$/' + 'rules' => 'required|regex:/^(tf)$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['tf2']->id, + 'env_variable' => 'SRCDS_MAP', + ], [ 'name' => 'Default Map', 'description' => 'The default map to use when starting the server.', - 'env_variable' => 'SRCDS_MAP', 'default_value' => 'cp_dustbowl', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^(\w{1,20})$/' + 'rules' => 'required|regex:/^(\w{1,20})$/', ]); } private function addArkVariables() { - DB::table('service_variables')->insert([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['ark']->id, + 'env_variable' => 'ARK_PASSWORD', + ], [ 'name' => 'Server Password', 'description' => 'If specified, players must provide this password to join the server.', - 'env_variable' => 'ARK_PASSWORD', 'default_value' => '', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 0, - 'regex' => '/^(\w\.*)$/' + 'rules' => 'alpha_dash|between:1,100', ]); - DB::table('service_variables')->insert([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['ark']->id, + 'env_variable' => 'ARK_ADMIN_PASSWORD', + ], [ 'name' => 'Admin Password', 'description' => 'If specified, players must provide this password (via the in-game console) to gain access to administrator commands on the server.', - 'env_variable' => 'ARK_ADMIN_PASSWORD', 'default_value' => '', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 0, - 'regex' => '/^(\w\.*)$/' + 'rules' => 'alpha_dash|between:1,100', ]); - DB::table('service_variables')->insert([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['ark']->id, + 'env_variable' => 'SERVER_MAX_PLAYERS', + ], [ 'name' => 'Maximum Players', 'description' => 'Specifies the maximum number of players that can play on the server simultaneously.', - 'env_variable' => 'SERVER_MAX_PLAYERS', 'default_value' => 20, 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^(\d{1,4})$/' + 'rules' => 'required|numeric|digits_between:1,4', ]); } private function addCustomVariables() { - Models\ServiceVariables::create([ - 'option_id' => $this->option['custom']->id, + ServiceVariable::updateOrCreate([ + 'option_id' => $this->option['source']->id, + 'env_variable' => 'SRCDS_APPID', + ], [ 'name' => 'Game ID', 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'env_variable' => 'SRCDS_APPID', 'default_value' => '', 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(\d){1,6}$/' + 'rules' => 'required|numeric|digits_between:1,6', ]); - Models\ServiceVariables::create([ - 'option_id' => $this->option['custom']->id, + ServiceVariable::updateOrCreate([ + 'option_id' => $this->option['source']->id, + 'env_variable' => 'SRCDS_GAME', + ], [ 'name' => 'Game Name', 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'env_variable' => 'SRCDS_GAME', 'default_value' => '', 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(.*)$/' + 'rules' => 'required|alpha_dash|between:1,100', ]); } } diff --git a/database/seeds/TerrariaServiceTableSeeder.php b/database/seeds/TerrariaServiceTableSeeder.php index 01c7376fe..6d451f12b 100644 --- a/database/seeds/TerrariaServiceTableSeeder.php +++ b/database/seeds/TerrariaServiceTableSeeder.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,8 +22,9 @@ * SOFTWARE. */ use Illuminate\Database\Seeder; - -use Pterodactyl\Models; +use Pterodactyl\Models\Service; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Models\ServiceVariable; class TerrariaServiceTableSeeder extends Seeder { @@ -35,7 +36,7 @@ class TerrariaServiceTableSeeder extends Seeder protected $service; /** - * Stores all of the option objects + * Stores all of the option objects. * * @var array */ @@ -55,53 +56,75 @@ class TerrariaServiceTableSeeder extends Seeder private function addCoreService() { - $this->service = Models\Service::create([ - 'author' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3', + $this->service = Service::updateOrCreate([ + 'author' => config('pterodactyl.service.core'), + 'folder' => 'terraria', + ], [ 'name' => 'Terraria', 'description' => 'Terraria is a land of adventure! A land of mystery! A land that\'s yours to shape, defend, and enjoy. Your options in Terraria are limitless. Are you an action gamer with an itchy trigger finger? A master builder? A collector? An explorer? There\'s something for everyone.', - 'file' => 'terraria', - 'executable' => 'TerrariaServer.exe', - 'startup' => '-port {{SERVER_PORT}} -autocreate 2 -worldname World' + 'startup' => 'mono TerrariaServer.exe -port {{SERVER_PORT}} -autocreate 2 -worldname World', + 'index_file' => Service::defaultIndexFile(), ]); } private function addCoreOptions() { - $this->option['tshock'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $script = <<<'EOF' +#!/bin/ash +# TShock Installation Script +# +# Server Files: /mnt/server +apk update +apk add curl unzip + +cd /tmp + +curl -sSLO https://github.com/NyxStudios/TShock/releases/download/v${T_VERSION}/tshock_${T_VERSION}.zip + +unzip -o tshock_${T_VERSION}.zip -d /mnt/server +EOF; + + $this->option['tshock'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'tshock', + ], [ 'name' => 'Terraria Server (TShock)', 'description' => 'TShock is a server modification for Terraria, written in C#, and based upon the Terraria Server API. It uses JSON for configuration management, and offers several features not present in the Terraria Server normally.', - 'tag' => 'tshock', - 'docker_image' => 'quay.io/pterodactyl/terraria:tshock', - 'executable' => '', - 'startup' => '' + 'docker_image' => 'quay.io/pterodactyl/core:mono', + 'config_startup' => '{"userInteraction": [ "You need to agree to the EULA"]}', + 'config_startup' => '{"done": "Type \'help\' for a list of commands", "userInteraction": []}', + 'config_files' => '{"tshock/config.json":{"parser": "json", "find":{"ServerPort": "{{server.build.default.port}}", "MaxSlots": "{{server.build.env.MAX_SLOTS}}"}}}', + 'config_logs' => '{"custom": false, "location": "ServerLog.txt"}', + 'config_stop' => 'exit', + 'startup' => null, + 'script_install' => $script, ]); } private function addVariables() { - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['tshock']->id, + 'env_variable' => 'T_VERSION', + ], [ 'name' => 'TShock Version', 'description' => 'Which version of TShock to install and use.', - 'env_variable' => 'T_VERSION', - 'default_value' => '4.3.17', + 'default_value' => '4.3.22', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^([0-9_\.-]{5,10})$/' + 'rules' => 'required|regex:/^([0-9_\.-]{5,10})$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['tshock']->id, + 'env_variable' => 'MAX_SLOTS', + ], [ 'name' => 'Maximum Slots', 'description' => 'Total number of slots to allow on the server.', - 'env_variable' => 'MAX_SLOTS', - 'default_value' => '20', + 'default_value' => 20, 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(\d){1,3}$/' + 'rules' => 'required|numeric|digits_between:1,3', ]); } } diff --git a/database/seeds/VoiceServiceTableSeeder.php b/database/seeds/VoiceServiceTableSeeder.php index 6756d0758..010302c95 100644 --- a/database/seeds/VoiceServiceTableSeeder.php +++ b/database/seeds/VoiceServiceTableSeeder.php @@ -1,7 +1,7 @@ + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,20 +22,21 @@ * SOFTWARE. */ use Illuminate\Database\Seeder; - -use Pterodactyl\Models; +use Pterodactyl\Models\Service; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Models\ServiceVariable; class VoiceServiceTableSeeder extends Seeder { /** * The core service ID. * - * @var Models\Service + * @var Service */ protected $service; /** - * Stores all of the option objects + * Stores all of the option objects. * * @var array */ @@ -55,75 +56,141 @@ class VoiceServiceTableSeeder extends Seeder private function addCoreService() { - $this->service = Models\Service::create([ - 'author' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3', + $this->service = Service::updateOrCreate([ + 'author' => config('pterodactyl.service.core'), + 'folder' => 'voice', + ], [ 'name' => 'Voice Servers', 'description' => 'Voice servers such as Mumble and Teamspeak 3.', - 'file' => 'voice', - 'executable' => '', - 'startup' => '' + 'startup' => '', + 'index_file' => Service::defaultIndexFile(), ]); } private function addCoreOptions() { - $this->option['mumble'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $script = <<<'EOF' +#!/bin/ash +# Mumble Installation Script +# +# Server Files: /mnt/server +apk update +apk add tar curl + +cd /tmp + +curl -sSLO https://github.com/mumble-voip/mumble/releases/download/${MUMBLE_VERSION}/murmur-static_x86-${MUMBLE_VERSION}.tar.bz2 + +tar -xjvf murmur-static_x86-${MUMBLE_VERSION}.tar.bz2 +cp -r murmur-static_x86-${MUMBLE_VERSION}/* /mnt/server +EOF; + + $this->option['mumble'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'mumble', + ], [ 'name' => 'Mumble Server', 'description' => 'Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.', - 'tag' => 'mumble', - 'docker_image' => 'quay.io/pterodactyl/voice:mumble', - 'executable' => './murmur.x86', - 'startup' => '-fg' + 'docker_image' => 'quay.io/pterodactyl/core:glibc', + 'config_startup' => '{"done": "Server listening on", "userInteraction": [ "Generating new server certificate"]}', + 'config_files' => '{"murmur.ini":{"parser": "ini", "find":{"logfile": "murmur.log", "port": "{{server.build.default.port}}", "host": "0.0.0.0", "users": "{{server.build.env.MAX_USERS}}"}}}', + 'config_logs' => '{"custom": true, "location": "logs/murmur.log"}', + 'config_stop' => '^C', + 'config_from' => null, + 'startup' => './murmur.x86 -fg', + 'script_install' => $script, ]); - $this->option['ts3'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $script = <<<'EOF' +#!/bin/ash +# TS3 Installation Script +# +# Server Files: /mnt/server +apk update +apk add tar curl + +cd /tmp + +curl -sSLO http://dl.4players.de/ts/releases/${TS_VERSION}/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 + +tar -xjvf teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 +cp -r teamspeak3-server_linux_amd64/* /mnt/server + +echo "machine_id= +default_voice_port=${SERVER_PORT} +voice_ip=0.0.0.0 +licensepath= +filetransfer_port=30033 +filetransfer_ip= +query_port=${SERVER_PORT} +query_ip=0.0.0.0 +query_ip_whitelist=query_ip_whitelist.txt +query_ip_blacklist=query_ip_blacklist.txt +dbplugin=ts3db_sqlite3 +dbpluginparameter= +dbsqlpath=sql/ +dbsqlcreatepath=create_sqlite/ +dbconnections=10 +logpath=logs +logquerycommands=0 +dbclientkeepdays=30 +logappend=0 +query_skipbruteforcecheck=0" > /mnt/server/ts3server.ini +EOF; + + $this->option['ts3'] = ServiceOption::updateOrCreate([ + 'service_id' => $this->service->id, + 'tag' => 'ts3', + ], [ 'name' => 'Teamspeak3 Server', 'description' => 'VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.', - 'tag' => 'ts3', - 'docker_image' => 'quay.io/pterodactyl/voice:ts3', - 'executable' => './ts3server_minimal_runscript.sh', - 'startup' => 'default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}}' + 'docker_image' => 'quay.io/pterodactyl/core:glibc', + 'config_startup' => '{"done": "listening on 0.0.0.0:", "userInteraction": []}', + 'config_files' => '{"ts3server.ini":{"parser": "ini", "find":{"default_voice_port": "{{server.build.default.port}}", "voice_ip": "0.0.0.0", "query_port": "{{server.build.default.port}}", "query_ip": "0.0.0.0"}}}', + 'config_logs' => '{"custom": true, "location": "logs/ts3.log"}', + 'config_stop' => '^C', + 'config_from' => null, + 'startup' => './ts3server_minimal_runscript.sh default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}}', + 'script_install' => $script, ]); } private function addVariables() { - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['mumble']->id, + 'env_variable' => 'MAX_USERS', + ], [ 'name' => 'Maximum Users', 'description' => 'Maximum concurrent users on the mumble server.', - 'env_variable' => 'MAX_USERS', - 'default_value' => '100', + 'default_value' => 100, 'user_viewable' => 1, 'user_editable' => 0, - 'required' => 1, - 'regex' => '/^(\d){1,6}$/' + 'rules' => 'required|numeric|digits_between:1,5', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['mumble']->id, + 'env_variable' => 'MUMBLE_VERSION', + ], [ 'name' => 'Server Version', 'description' => 'Version of Mumble Server to download and use.', - 'env_variable' => 'MUMBLE_VERSION', - 'default_value' => '1.2.16', + 'default_value' => '1.2.19', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^([0-9_\.-]{5,8})$/' + 'rules' => 'required|regex:/^([0-9_\.-]{5,8})$/', ]); - Models\ServiceVariables::create([ + ServiceVariable::updateOrCreate([ 'option_id' => $this->option['ts3']->id, + 'env_variable' => 'TS_VERSION', + ], [ 'name' => 'Server Version', 'description' => 'The version of Teamspeak 3 to use when running the server.', - 'env_variable' => 'T_VERSION', - 'default_value' => '3.0.13.4', + 'default_value' => '3.0.13.6', 'user_viewable' => 1, 'user_editable' => 1, - 'required' => 1, - 'regex' => '/^([0-9_\.-]{5,10})$/' + 'rules' => 'required|regex:/^([0-9_\.-]{5,10})$/', ]); } } diff --git a/package.json b/package.json new file mode 100644 index 000000000..2a9697409 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "pterodactyl-panel", + "devDependencies": { + "babel-cli": "6.18.0", + "babel-plugin-transform-strict-mode": "^6.18.0", + "babel-preset-es2015": "6.18.0" + }, + "scripts": { + "build": "./node_modules/babel-cli/bin/babel.js public/themes/pterodactyl/js/frontend/files/src --source-maps --out-file public/themes/pterodactyl/js/frontend/files/filemanager.min.js" + } +} diff --git a/public/.htaccess b/public/.htaccess index 8eb2dd0dd..342448645 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -8,6 +8,10 @@ # Redirect Trailing Slashes If Not A Folder... RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)/$ /$1 [L,R=301] + + # Prevent stripping authorization headers + RewriteCond %{HTTP:Authorization} ^(.*) + RewriteRule .* - [e=HTTP_AUTHORIZATION:%1] # Handle Front Controller... RewriteCond %{REQUEST_FILENAME} !-d diff --git a/public/css/animate.css b/public/css/animate.css deleted file mode 100755 index 441e618ab..000000000 --- a/public/css/animate.css +++ /dev/null @@ -1,645 +0,0 @@ -@charset "UTF-8"; - -/*! -Animate.css - http://daneden.me/animate -Licensed under the MIT license - http://opensource.org/licenses/MIT - -Copyright (c) 2015 Daniel Eden -*/ - -.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both} -.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite} -.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s} -.animated.bounceIn,.animated.bounceOut,.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s} -@-webkit-keyframes bounce{0%,100%,20%,53%,80%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -40%,43%{-webkit-transition-timing-function:cubic-bezier(.755,.050,.855,.060);transition-timing-function:cubic-bezier(.755,.050,.855,.060);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)} -70%{-webkit-transition-timing-function:cubic-bezier(.755,.050,.855,.060);transition-timing-function:cubic-bezier(.755,.050,.855,.060);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)} -90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)} -} -@keyframes bounce{0%,100%,20%,53%,80%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -40%,43%{-webkit-transition-timing-function:cubic-bezier(.755,.050,.855,.060);transition-timing-function:cubic-bezier(.755,.050,.855,.060);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)} -70%{-webkit-transition-timing-function:cubic-bezier(.755,.050,.855,.060);transition-timing-function:cubic-bezier(.755,.050,.855,.060);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)} -90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)} -} -.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom} -@-webkit-keyframes flash{0%,100%,50%{opacity:1} -25%,75%{opacity:0} -} -@keyframes flash{0%,100%,50%{opacity:1} -25%,75%{opacity:0} -} -.flash{-webkit-animation-name:flash;animation-name:flash} -@-webkit-keyframes pulse{0%,100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)} -50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)} -} -@keyframes pulse{0%,100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)} -50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)} -} -.pulse{-webkit-animation-name:pulse;animation-name:pulse} -@-webkit-keyframes rubberBand{0%,100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)} -30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)} -40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)} -50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)} -65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)} -75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)} -} -@keyframes rubberBand{0%,100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)} -30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)} -40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)} -50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)} -65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)} -75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)} -} -.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand} -@-webkit-keyframes shake{0%,100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)} -20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)} -} -@keyframes shake{0%,100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)} -20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)} -} -.shake{-webkit-animation-name:shake;animation-name:shake} -@-webkit-keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)} -40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)} -60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)} -80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)} -100%{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)} -} -@keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)} -40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)} -60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)} -80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)} -100%{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)} -} -.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing} -@-webkit-keyframes tada{0%,100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)} -10%,20%{-webkit-transform:scale3d(.9,.9,.9)rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9)rotate3d(0,0,1,-3deg)} -30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,3deg)} -40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,-3deg)} -} -@keyframes tada{0%,100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)} -10%,20%{-webkit-transform:scale3d(.9,.9,.9)rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9)rotate3d(0,0,1,-3deg)} -30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,3deg)} -40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,-3deg)} -} -.tada{-webkit-animation-name:tada;animation-name:tada} -@-webkit-keyframes wobble{0%,100%{-webkit-transform:none;transform:none} -15%{-webkit-transform:translate3d(-25%,0,0)rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0)rotate3d(0,0,1,-5deg)} -30%{-webkit-transform:translate3d(20%,0,0)rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0)rotate3d(0,0,1,3deg)} -45%{-webkit-transform:translate3d(-15%,0,0)rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0)rotate3d(0,0,1,-3deg)} -60%{-webkit-transform:translate3d(10%,0,0)rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0)rotate3d(0,0,1,2deg)} -75%{-webkit-transform:translate3d(-5%,0,0)rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0)rotate3d(0,0,1,-1deg)} -} -@keyframes wobble{0%,100%{-webkit-transform:none;transform:none} -15%{-webkit-transform:translate3d(-25%,0,0)rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0)rotate3d(0,0,1,-5deg)} -30%{-webkit-transform:translate3d(20%,0,0)rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0)rotate3d(0,0,1,3deg)} -45%{-webkit-transform:translate3d(-15%,0,0)rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0)rotate3d(0,0,1,-3deg)} -60%{-webkit-transform:translate3d(10%,0,0)rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0)rotate3d(0,0,1,2deg)} -75%{-webkit-transform:translate3d(-5%,0,0)rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0)rotate3d(0,0,1,-1deg)} -} -.wobble{-webkit-animation-name:wobble;animation-name:wobble} -@-webkit-keyframes bounceIn{0%,100%,20%,40%,60%,80%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)} -20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)} -40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)} -60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)} -80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)} -100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)} -} -@keyframes bounceIn{0%,100%,20%,40%,60%,80%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)} -20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)} -40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)} -60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)} -80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)} -100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)} -} -.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn} -@-webkit-keyframes bounceInDown{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)} -60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)} -75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)} -90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)} -100%{-webkit-transform:none;transform:none} -} -@keyframes bounceInDown{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)} -60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)} -75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)} -90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)} -100%{-webkit-transform:none;transform:none} -} -.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown} -@-webkit-keyframes bounceInLeft{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)} -60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)} -75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)} -90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)} -100%{-webkit-transform:none;transform:none} -} -@keyframes bounceInLeft{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)} -60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)} -75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)} -90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)} -100%{-webkit-transform:none;transform:none} -} -.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft} -@-webkit-keyframes bounceInRight{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)} -60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)} -75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)} -90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)} -100%{-webkit-transform:none;transform:none} -} -@keyframes bounceInRight{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)} -60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)} -75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)} -90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)} -100%{-webkit-transform:none;transform:none} -} -.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight} -@-webkit-keyframes bounceInUp{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)} -60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)} -75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)} -90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -@keyframes bounceInUp{0%,100%,60%,75%,90%{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)} -0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)} -60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)} -75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)} -90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp} -@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)} -50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)} -100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)} -} -@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)} -50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)} -100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)} -} -.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut} -@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)} -40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)} -100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)} -} -@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)} -40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)} -100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)} -} -.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown} -@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)} -100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)} -} -@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)} -100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)} -} -.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft} -@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)} -100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)} -} -@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)} -100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)} -} -.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight} -@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)} -40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)} -100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)} -} -@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)} -40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)} -100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)} -} -.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp} -@-webkit-keyframes fadeIn{0%{opacity:0} -100%{opacity:1} -} -@keyframes fadeIn{0%{opacity:0} -100%{opacity:1} -} -.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn} -@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown} -@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig} -@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft} -@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig} -@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight} -@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig} -@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp} -@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig} -@-webkit-keyframes fadeOut{0%{opacity:1} -100%{opacity:0} -} -@keyframes fadeOut{0%{opacity:1} -100%{opacity:0} -} -.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut} -@-webkit-keyframes fadeOutDown{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)} -} -@keyframes fadeOutDown{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)} -} -.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown} -@-webkit-keyframes fadeOutDownBig{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)} -} -@keyframes fadeOutDownBig{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)} -} -.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig} -@-webkit-keyframes fadeOutLeft{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)} -} -@keyframes fadeOutLeft{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)} -} -.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft} -@-webkit-keyframes fadeOutLeftBig{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)} -} -@keyframes fadeOutLeftBig{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)} -} -.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig} -@-webkit-keyframes fadeOutRight{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)} -} -@keyframes fadeOutRight{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)} -} -.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight} -@-webkit-keyframes fadeOutRightBig{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)} -} -@keyframes fadeOutRightBig{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)} -} -.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig} -@-webkit-keyframes fadeOutUp{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)} -} -@keyframes fadeOutUp{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)} -} -.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp} -@-webkit-keyframes fadeOutUpBig{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)} -} -@keyframes fadeOutUpBig{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)} -} -.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig} -@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-360deg);transform:perspective(400px)rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out} -40%{-webkit-transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-190deg);transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out} -50%{-webkit-transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-170deg);transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in} -80%{-webkit-transform:perspective(400px)scale3d(.95,.95,.95);transform:perspective(400px)scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in} -100%{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in} -} -@keyframes flip{0%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-360deg);transform:perspective(400px)rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out} -40%{-webkit-transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-190deg);transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out} -50%{-webkit-transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-170deg);transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in} -80%{-webkit-transform:perspective(400px)scale3d(.95,.95,.95);transform:perspective(400px)scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in} -100%{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in} -} -.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip} -@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px)rotate3d(1,0,0,90deg);transform:perspective(400px)rotate3d(1,0,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0} -40%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-20deg);transform:perspective(400px)rotate3d(1,0,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in} -60%{-webkit-transform:perspective(400px)rotate3d(1,0,0,10deg);transform:perspective(400px)rotate3d(1,0,0,10deg);opacity:1} -80%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-5deg);transform:perspective(400px)rotate3d(1,0,0,-5deg)} -100%{-webkit-transform:perspective(400px);transform:perspective(400px)} -} -@keyframes flipInX{0%{-webkit-transform:perspective(400px)rotate3d(1,0,0,90deg);transform:perspective(400px)rotate3d(1,0,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0} -40%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-20deg);transform:perspective(400px)rotate3d(1,0,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in} -60%{-webkit-transform:perspective(400px)rotate3d(1,0,0,10deg);transform:perspective(400px)rotate3d(1,0,0,10deg);opacity:1} -80%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-5deg);transform:perspective(400px)rotate3d(1,0,0,-5deg)} -100%{-webkit-transform:perspective(400px);transform:perspective(400px)} -} -.flipInX{backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX} -.flipInX,.flipInY{-webkit-backface-visibility:visible!important} -@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px)rotate3d(0,1,0,90deg);transform:perspective(400px)rotate3d(0,1,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0} -40%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-20deg);transform:perspective(400px)rotate3d(0,1,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in} -60%{-webkit-transform:perspective(400px)rotate3d(0,1,0,10deg);transform:perspective(400px)rotate3d(0,1,0,10deg);opacity:1} -80%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-5deg);transform:perspective(400px)rotate3d(0,1,0,-5deg)} -100%{-webkit-transform:perspective(400px);transform:perspective(400px)} -} -@keyframes flipInY{0%{-webkit-transform:perspective(400px)rotate3d(0,1,0,90deg);transform:perspective(400px)rotate3d(0,1,0,90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0} -40%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-20deg);transform:perspective(400px)rotate3d(0,1,0,-20deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in} -60%{-webkit-transform:perspective(400px)rotate3d(0,1,0,10deg);transform:perspective(400px)rotate3d(0,1,0,10deg);opacity:1} -80%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-5deg);transform:perspective(400px)rotate3d(0,1,0,-5deg)} -100%{-webkit-transform:perspective(400px);transform:perspective(400px)} -} -.flipInY{backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY} -@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)} -30%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-20deg);transform:perspective(400px)rotate3d(1,0,0,-20deg);opacity:1} -100%{-webkit-transform:perspective(400px)rotate3d(1,0,0,90deg);transform:perspective(400px)rotate3d(1,0,0,90deg);opacity:0} -} -@keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)} -30%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-20deg);transform:perspective(400px)rotate3d(1,0,0,-20deg);opacity:1} -100%{-webkit-transform:perspective(400px)rotate3d(1,0,0,90deg);transform:perspective(400px)rotate3d(1,0,0,90deg);opacity:0} -} -.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;backface-visibility:visible!important} -.flipOutX,.flipOutY{-webkit-backface-visibility:visible!important} -@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)} -30%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-15deg);transform:perspective(400px)rotate3d(0,1,0,-15deg);opacity:1} -100%{-webkit-transform:perspective(400px)rotate3d(0,1,0,90deg);transform:perspective(400px)rotate3d(0,1,0,90deg);opacity:0} -} -@keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)} -30%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-15deg);transform:perspective(400px)rotate3d(0,1,0,-15deg);opacity:1} -100%{-webkit-transform:perspective(400px)rotate3d(0,1,0,90deg);transform:perspective(400px)rotate3d(0,1,0,90deg);opacity:0} -} -.flipOutY{backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY} -@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0)skewX(-30deg);transform:translate3d(100%,0,0)skewX(-30deg);opacity:0} -60%{-webkit-transform:skewX(20deg);transform:skewX(20deg);opacity:1} -80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1} -100%{-webkit-transform:none;transform:none;opacity:1} -} -@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0)skewX(-30deg);transform:translate3d(100%,0,0)skewX(-30deg);opacity:0} -60%{-webkit-transform:skewX(20deg);transform:skewX(20deg);opacity:1} -80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1} -100%{-webkit-transform:none;transform:none;opacity:1} -} -.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out} -@-webkit-keyframes lightSpeedOut{0%{opacity:1} -100%{-webkit-transform:translate3d(100%,0,0)skewX(30deg);transform:translate3d(100%,0,0)skewX(30deg);opacity:0} -} -@keyframes lightSpeedOut{0%{opacity:1} -100%{-webkit-transform:translate3d(100%,0,0)skewX(30deg);transform:translate3d(100%,0,0)skewX(30deg);opacity:0} -} -.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in} -@-webkit-keyframes rotateIn{0%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0} -100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:none;transform:none;opacity:1} -} -@keyframes rotateIn{0%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0} -100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:none;transform:none;opacity:1} -} -.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn} -@-webkit-keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0} -100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1} -} -@keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0} -100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1} -} -.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft} -@-webkit-keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0} -100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1} -} -@keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0} -100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1} -} -.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight} -@-webkit-keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0} -100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1} -} -@keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0} -100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1} -} -.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft} -@-webkit-keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0} -100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1} -} -@keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0} -100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1} -} -.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight} -@-webkit-keyframes rotateOut{0%{-webkit-transform-origin:center;transform-origin:center;opacity:1} -100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0} -} -@keyframes rotateOut{0%{-webkit-transform-origin:center;transform-origin:center;opacity:1} -100%{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0} -} -.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut} -@-webkit-keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1} -100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0} -} -@keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1} -100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0} -} -.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft} -@-webkit-keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1} -100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0} -} -@keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1} -100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0} -} -.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight} -@-webkit-keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1} -100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0} -} -@keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1} -100%{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0} -} -.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft} -@-webkit-keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1} -100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0} -} -@keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1} -100%{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0} -} -.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight} -@-webkit-keyframes hinge{0%{-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out} -20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out} -40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1} -100%{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0} -} -@keyframes hinge{0%{-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out} -20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out} -40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1} -100%{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0} -} -.hinge{-webkit-animation-name:hinge;animation-name:hinge} -@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0)rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0)rotate3d(0,0,1,-120deg)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0)rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0)rotate3d(0,0,1,-120deg)} -100%{opacity:1;-webkit-transform:none;transform:none} -} -.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn} -@-webkit-keyframes rollOut{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(100%,0,0)rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0)rotate3d(0,0,1,120deg)} -} -@keyframes rollOut{0%{opacity:1} -100%{opacity:0;-webkit-transform:translate3d(100%,0,0)rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0)rotate3d(0,0,1,120deg)} -} -.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut} -@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)} -50%{opacity:1} -} -@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)} -50%{opacity:1} -} -.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn} -@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1)translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,60px,0);transform:scale3d(.475,.475,.475)translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1)translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,60px,0);transform:scale3d(.475,.475,.475)translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown} -@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1)translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(10px,0,0);transform:scale3d(.475,.475,.475)translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1)translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(10px,0,0);transform:scale3d(.475,.475,.475)translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft} -@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(1000px,0,0);transform:scale3d(.1,.1,.1)translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(-10px,0,0);transform:scale3d(.475,.475,.475)translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(1000px,0,0);transform:scale3d(.1,.1,.1)translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(-10px,0,0);transform:scale3d(.475,.475,.475)translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight} -@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,1000px,0);transform:scale3d(.1,.1,.1)translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,1000px,0);transform:scale3d(.1,.1,.1)translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp} -@-webkit-keyframes zoomOut{0%{opacity:1} -50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)} -100%{opacity:0} -} -@keyframes zoomOut{0%{opacity:1} -50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)} -100%{opacity:0} -} -.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut} -@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,2000px,0);transform:scale3d(.1,.1,.1)translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,2000px,0);transform:scale3d(.1,.1,.1)translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown} -@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(42px,0,0);transform:scale3d(.475,.475,.475)translate3d(42px,0,0)} -100%{opacity:0;-webkit-transform:scale(.1)translate3d(-2000px,0,0);transform:scale(.1)translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center} -} -@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(42px,0,0);transform:scale3d(.475,.475,.475)translate3d(42px,0,0)} -100%{opacity:0;-webkit-transform:scale(.1)translate3d(-2000px,0,0);transform:scale(.1)translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center} -} -.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft} -@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(-42px,0,0);transform:scale3d(.475,.475,.475)translate3d(-42px,0,0)} -100%{opacity:0;-webkit-transform:scale(.1)translate3d(2000px,0,0);transform:scale(.1)translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center} -} -@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(-42px,0,0);transform:scale3d(.475,.475,.475)translate3d(-42px,0,0)} -100%{opacity:0;-webkit-transform:scale(.1)translate3d(2000px,0,0);transform:scale(.1)translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center} -} -.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight} -@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,60px,0);transform:scale3d(.475,.475,.475)translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1)translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,60px,0);transform:scale3d(.475,.475,.475)translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)} -100%{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1)translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)} -} -.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp} -@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown} -@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft} -@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight} -@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible} -100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp} -@-webkit-keyframes slideOutDown{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -100%{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)} -} -@keyframes slideOutDown{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -100%{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)} -} -.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown} -@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -100%{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)} -} -@keyframes slideOutLeft{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -100%{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)} -} -.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft} -@-webkit-keyframes slideOutRight{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -100%{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)} -} -@keyframes slideOutRight{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -100%{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)} -} -.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight} -@-webkit-keyframes slideOutUp{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -100%{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)} -} -@keyframes slideOutUp{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -100%{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)} -} -.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp} \ No newline at end of file diff --git a/public/css/jquery.terminal.css b/public/css/jquery.terminal.css deleted file mode 100755 index c838aa0d3..000000000 --- a/public/css/jquery.terminal.css +++ /dev/null @@ -1,253 +0,0 @@ -/*! - * __ _____ ________ __ - * / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / / - * __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ / - * / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__ - * \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/ - * \/ /____/ version 0.11.6 - * http://terminal.jcubic.pl - * - * This file is part of jQuery Terminal. - * - * Copyright (c) 2011-2016 Jakub Jankiewicz - * Released under the MIT license - * - * Date: Thu, 15 Sep 2016 20:19:20 +0000 - */ -.terminal .terminal-output .format, .cmd .format, -.cmd .prompt, .cmd .prompt div, .terminal .terminal-output div div{ - display: inline-block; -} -.terminal h1, .terminal h2, .terminal h3, .terminal h4, .terminal h5, .terminal h6, .terminal pre, .cmd { - margin: 0; -} -.terminal h1, .terminal h2, .terminal h3, .terminal h4, .terminal h5, .terminal h6 { - line-height: 1.2em; -} -/* -.cmd .mask { - width: 10px; - height: 11px; - background: black; - z-index: 100; -} -*/ -.cmd .clipboard { - position: absolute; - left: -16px; - top: 0; - width: 10px; - height: 16px; - /* this seems to work after all on Android */ - /*left: -99999px; - clip: rect(1px,1px,1px,1px); - /* on desktop textarea appear when paste */ - /* - opacity: 0.01; - filter: alpha(opacity = 0.01); - filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0.01); - */ - background: transparent; - border: none; - color: transparent; - outline: none; - padding: 0; - resize: none; - z-index: 0; - overflow: hidden; -} -.terminal .error { - color: #f00; -} -.terminal { - padding: 10px; - position: relative; - /*overflow: hidden;*/ - overflow: auto; -} -.cmd { - padding: 0; - height: 1.3em; - position: relative; - /*margin-top: 3px; */ -} -.terminal .inverted, .cmd .inverted, .cmd .cursor.blink { - background-color: #aaa; - color: #000; -} -.cmd .cursor.blink { - -webkit-animation: terminal-blink 1s infinite steps(1, start); - -moz-animation: terminal-blink 1s infinite steps(1, start); - -ms-animation: terminal-blink 1s infinite steps(1, start); - animation: terminal-blink 1s infinite steps(1, start); -} -@-webkit-keyframes terminal-blink { - 0%, 100% { - background-color: #000; - color: #aaa; - } - 50% { - background-color: #bbb; - color: #000; - } -} - -@-ms-keyframes terminal-blink { - 0%, 100% { - background-color: #000; - color: #aaa; - } - 50% { - background-color: #bbb; - color: #000; - } -} - -@-moz-keyframes terminal-blink { - 0%, 100% { - background-color: #000; - color: #aaa; - } - 50% { - background-color: #bbb; - color: #000; - } -} -@keyframes terminal-blink { - 0%, 100% { - background-color: #000; - color: #aaa; - } - 50% { - background-color: #bbb; /* not #aaa because it's seems there is Google Chrome bug */ - color: #000; - } -} - -.terminal .terminal-output div div, .cmd .prompt { - display: block; - line-height: 14px; - height: auto; -} -.cmd .prompt { - float: left; -} -.terminal, .cmd { - font-family: monospace; - /*font-family: FreeMono, monospace; this don't work on Android */ - color: #aaa; - background-color: #000; - font-size: 12px; - line-height: 14px; -} -.terminal-output > div { - /*padding-top: 3px;*/ - min-height: 14px; -} -.terminal-output > div > div * { - word-wrap: break-word; /* when echo html */ -} -.terminal .terminal-output div span { - display: inline-block; -} -.cmd span { - float: left; - /*display: inline-block; */ -} -/* fix double style of selecting text in terminal */ -.terminal-output span, .terminal-output a, .cmd div, .cmd span, .terminal td, -.terminal pre, .terminal h1, .terminal h2, .terminal h3, .terminal h4, -.terminal h5, .terminal h6 { - -webkit-touch-callout: initial; - -webkit-user-select: initial; - -khtml-user-select: initial; - -moz-user-select: initial; - -ms-user-select: initial; - user-select: initial; -} -.terminal, .terminal-output, .terminal-output div { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -/* firefox hack */ -@-moz-document url-prefix() { - .terminal, .terminal-output, .terminal-output div { - -webkit-touch-callout: initial; - -webkit-user-select: initial; - -khtml-user-select: initial; - -moz-user-select: initial; - -ms-user-select: initial; - user-select: initial; - } -} -.terminal table { - border-collapse: collapse; -} -.terminal td { - border: 1px solid #aaa; -} -.terminal h1::-moz-selection, -.terminal h2::-moz-selection, -.terminal h3::-moz-selection, -.terminal h4::-moz-selection, -.terminal h5::-moz-selection, -.terminal h6::-moz-selection, -.terminal pre::-moz-selection, -.terminal td::-moz-selection, -.terminal .terminal-output div div::-moz-selection, -.terminal .terminal-output div span::-moz-selection, -.terminal .terminal-output div div a::-moz-selection, -.cmd div::-moz-selection, -.cmd > span::-moz-selection, -.cmd .prompt span::-moz-selection { - background-color: #aaa; - color: #000; -} -/* this don't work in Chrome -.terminal tr td::-moz-selection { - border-color: #000; -} -.terminal tr td::selection { - border-color: #000; -} -*/ -.terminal h1::selection, -.terminal h2::selection, -.terminal h3::selection, -.terminal h4::selection, -.terminal h5::selection, -.terminal h6::selection, -.terminal pre::selection, -.terminal td::selection, -.terminal .terminal-output div div::selection, -.terminal .terminal-output div div a::selection, -.terminal .terminal-output div span::selection, -.cmd div::selection, -.cmd > span::selection, -.cmd .prompt span::selection { - background-color: #aaa; - color: #000; -} -.terminal .terminal-output div.error, .terminal .terminal-output div.error div { - color: red; -} -.tilda { - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 1100; -} -.clear { - clear: both; -} -.terminal a { - color: #0F60FF; -} -.terminal a:hover { - color: red; -} diff --git a/public/css/metricsgraphics.css b/public/css/metricsgraphics.css deleted file mode 100755 index b523ab4ad..000000000 --- a/public/css/metricsgraphics.css +++ /dev/null @@ -1,369 +0,0 @@ - /*v 2.8*/ -.mg-active-datapoint { - fill: black; - font-size: 0.9rem; - font-weight: 400; - opacity: 0.8; -} - -.mg-area1-color { - fill: #0000ff; -} - -.mg-area2-color { - fill: #05b378; -} - -.mg-area3-color { - fill: #db4437; -} - -.mg-area4-color { - fill: #f8b128; -} - -.mg-area5-color { - fill: #5c5c5c; -} - -.mg-barplot rect.mg-bar { - shape-rendering: auto; - fill: #b6b6fc; -} - -.mg-barplot rect.mg-bar.active { - fill: #9e9efc; -} - -.mg-barplot .mg-bar-prediction { - fill: #5b5b5b; -} - -.mg-barplot .mg-bar-baseline { - stroke: #5b5b5b; - stroke-width: 2; -} - -.mg-baselines line { - opacity: 1; - shape-rendering: auto; - stroke: #b3b2b2; - stroke-width: 1px; -} - -.mg-baselines text { - fill: black; - font-size: 0.9rem; - opacity: 0.6; - stroke: none; -} - -.mg-baselines-small text { - font-size: 0.6rem; -} - -.mg-header { - cursor: default; - font-size: 1.2rem; -} - -.mg-header .mg-chart-description { - fill: #ccc; - font-family: FontAwesome; - font-size: 1.2rem; -} - -.mg-points circle { - opacity: 0.65; -} - -.mg-popover { - font-size: 0.95rem; -} - -.mg-popover-content { - cursor: auto; - line-height: 17px; -} - -.mg-data-table { - margin-top: 30px; -} - -.mg-data-table thead tr th { - border-bottom: 1px solid darkgray; - cursor: default; - font-size: 1.1rem; - font-weight: normal; - padding: 5px 5px 8px 5px; - text-align: right; -} - -.mg-data-table thead tr th .fa { - color: #ccc; - padding-left: 4px; -} - -.mg-data-table thead tr th .popover { - font-size: 1rem; - font-weight: normal; -} - -.mg-data-table .secondary-title { - color: darkgray; -} - -.mg-data-table tbody tr td { - margin: 2px; - padding: 5px; - vertical-align: top; -} - -.mg-data-table tbody tr td.table-text { - opacity: 0.8; - padding-left: 30px; -} - -.mg-y-axis line.mg-extended-y-ticks { - opacity: 0.4; -} - -.mg-x-axis line.mg-extended-x-ticks { - opacity: 0.4; -} - -.mg-histogram .axis path, -.mg-histogram .axis line { - fill: none; - opacity: 0.7; - shape-rendering: auto; - stroke: #ccc; -} - -.mg-histogram .mg-bar rect { - fill: #b6b6fc; - shape-rendering: auto; -} - -.mg-histogram .mg-bar rect.active { - fill: #9e9efc; -} - -.mg-least-squares-line { - stroke: red; - stroke-width: 1px; -} - -.mg-lowess-line { - fill: none; - stroke: red; -} - -.mg-line1-color { - stroke: #4040e8; -} - -.mg-hover-line1-color { - fill: #4040e8; -} - -.mg-line2-color { - stroke: #05b378; -} - -.mg-hover-line2-color { - fill: #05b378; -} - -.mg-line3-color { - stroke: #db4437; -} - -.mg-hover-line3-color { - fill: #db4437; -} - -.mg-line4-color { - stroke: #f8b128; -} - -.mg-hover-line4-color { - fill: #f8b128; -} - -.mg-line5-color { - stroke: #5c5c5c; -} - -.mg-hover-line5-color { - fill: #5c5c5c; -} - -.mg-line-legend text { - font-size: 0.9rem; - font-weight: 300; - stroke: none; -} - -.mg-line1-legend-color { - color: #4040e8; - fill: #4040e8; -} - -.mg-line2-legend-color { - color: #05b378; - fill: #05b378; -} - -.mg-line3-legend-color { - color: #db4437; - fill: #db4437; -} - -.mg-line4-legend-color { - color: #f8b128; - fill: #f8b128; -} - -.mg-line5-legend-color { - color: #5c5c5c; - fill: #5c5c5c; -} - -.mg-main-area-solid svg .mg-main-area { - fill: #ccccff; - opacity: 1; -} - -.mg-markers line { - opacity: 1; - shape-rendering: auto; - stroke: #b3b2b2; - stroke-width: 1px; -} - -.mg-markers text { - fill: black; - font-size: 0.8rem; - opacity: 0.6; -} - -.mg-missing-text { - opacity: 0.9; -} - -.mg-missing-background { - stroke: blue; - fill: none; - stroke-dasharray: 10,5; - stroke-opacity: 0.05; - stroke-width: 2; -} - -.mg-missing .mg-main-line { - opacity: 0.1; -} - -.mg-missing .mg-main-area { - opacity: 0.03; -} - -path.mg-main-area { - opacity: 0.2; - stroke: none; -} - -path.mg-confidence-band { - fill: #ccc; - opacity: 0.4; - stroke: none; -} - -path.mg-main-line { - fill: none; - opacity: 0.8; - stroke-width: 1.1px; -} - -.mg-points circle { - fill-opacity: 0.4; - stroke-opacity: 1; -} - -circle.mg-points-mono { - fill: #0000ff; - stroke: #0000ff; -} - -/* a selected point in a scatterplot */ -.mg-points circle.selected { - fill-opacity: 1; - stroke-opacity: 1; -} - -.mg-voronoi path { - fill: none; - pointer-events: all; - stroke: none; - stroke-opacity: 0.1; -} - -.mg-x-rug-mono, -.mg-y-rug-mono { - stroke: black; -} - -.mg-x-axis line, -.mg-y-axis line { - opacity: 1; - shape-rendering: auto; - stroke: #b3b2b2; - stroke-width: 1px; -} - -.mg-x-axis text, -.mg-y-axis text, -.mg-histogram .axis text { - fill: black; - font-size: 0.9rem; - opacity: 0.6; -} - -.mg-x-axis .label, -.mg-y-axis .label, -.mg-axis .label { - font-size: 0.8rem; - text-transform: uppercase; - font-weight: 400; -} - -.mg-x-axis-small text, -.mg-y-axis-small text, -.mg-active-datapoint-small { - font-size: 0.6rem; -} - -.mg-x-axis-small .label, -.mg-y-axis-small .label { - font-size: 0.65rem; -} - -.mg-european-hours { -} - -.mg-year-marker text { - fill: black; - font-size: 0.7rem; - opacity: 0.6; -} - -.mg-year-marker line { - opacity: 1; - shape-rendering: auto; - stroke: #b3b2b2; - stroke-width: 1px; -} - -.mg-year-marker-small text { - font-size: 0.6rem; -} diff --git a/public/css/vendor/fontawesome/font-awesome.min.css b/public/css/vendor/fontawesome/font-awesome.min.css deleted file mode 100755 index acce2ef23..000000000 --- a/public/css/vendor/fontawesome/font-awesome.min.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('fonts/fontawesome-webfont.eot?v=4.5.0');src:url('fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} diff --git a/public/css/vendor/fontawesome/fonts/FontAwesome.otf b/public/css/vendor/fontawesome/fonts/FontAwesome.otf deleted file mode 100755 index 3ed7f8b48..000000000 Binary files a/public/css/vendor/fontawesome/fonts/FontAwesome.otf and /dev/null differ diff --git a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.eot b/public/css/vendor/fontawesome/fonts/fontawesome-webfont.eot deleted file mode 100755 index 9b6afaedc..000000000 Binary files a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.svg b/public/css/vendor/fontawesome/fonts/fontawesome-webfont.svg deleted file mode 100755 index d05688e9e..000000000 --- a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,655 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.ttf b/public/css/vendor/fontawesome/fonts/fontawesome-webfont.ttf deleted file mode 100755 index 26dea7951..000000000 Binary files a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.woff b/public/css/vendor/fontawesome/fonts/fontawesome-webfont.woff deleted file mode 100755 index dc35ce3c2..000000000 Binary files a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.woff2 b/public/css/vendor/fontawesome/fonts/fontawesome-webfont.woff2 deleted file mode 100755 index 500e51725..000000000 Binary files a/public/css/vendor/fontawesome/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/public/favicons/android-chrome-192x192.png b/public/favicons/android-chrome-192x192.png new file mode 100644 index 000000000..aed30b263 Binary files /dev/null and b/public/favicons/android-chrome-192x192.png differ diff --git a/public/favicons/android-chrome-512x512.png b/public/favicons/android-chrome-512x512.png new file mode 100644 index 000000000..5ca796ca7 Binary files /dev/null and b/public/favicons/android-chrome-512x512.png differ diff --git a/public/favicons/apple-touch-icon.png b/public/favicons/apple-touch-icon.png new file mode 100644 index 000000000..5ec6cfcc2 Binary files /dev/null and b/public/favicons/apple-touch-icon.png differ diff --git a/public/favicons/browserconfig.xml b/public/favicons/browserconfig.xml new file mode 100644 index 000000000..e3cb776ee --- /dev/null +++ b/public/favicons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #165ed4 + + + diff --git a/public/favicons/favicon-16x16.png b/public/favicons/favicon-16x16.png new file mode 100644 index 000000000..d568bd20a Binary files /dev/null and b/public/favicons/favicon-16x16.png differ diff --git a/public/favicons/favicon-32x32.png b/public/favicons/favicon-32x32.png new file mode 100644 index 000000000..edfd13405 Binary files /dev/null and b/public/favicons/favicon-32x32.png differ diff --git a/public/favicons/favicon.ico b/public/favicons/favicon.ico new file mode 100644 index 000000000..99e5bffe2 Binary files /dev/null and b/public/favicons/favicon.ico differ diff --git a/public/favicons/manifest.json b/public/favicons/manifest.json new file mode 100644 index 000000000..796d1ff0c --- /dev/null +++ b/public/favicons/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "", + "icons": [ + { + "src": "favicons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "favicons/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/public/favicons/mstile-150x150.png b/public/favicons/mstile-150x150.png new file mode 100644 index 000000000..9a8fa880b Binary files /dev/null and b/public/favicons/mstile-150x150.png differ diff --git a/public/favicons/safari-pinned-tab.svg b/public/favicons/safari-pinned-tab.svg new file mode 100644 index 000000000..fd23e1d96 --- /dev/null +++ b/public/favicons/safari-pinned-tab.svg @@ -0,0 +1,475 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/public/index.php b/public/index.php index c5820533b..f2bd8213e 100644 --- a/public/index.php +++ b/public/index.php @@ -1,9 +1,8 @@ */ @@ -19,7 +18,7 @@ | */ -require __DIR__.'/../bootstrap/autoload.php'; +require __DIR__ . '/../bootstrap/autoload.php'; /* |-------------------------------------------------------------------------- @@ -33,7 +32,7 @@ require __DIR__.'/../bootstrap/autoload.php'; | */ -$app = require_once __DIR__.'/../bootstrap/app.php'; +$app = require_once __DIR__ . '/../bootstrap/app.php'; /* |-------------------------------------------------------------------------- diff --git a/public/js/admin.min.js b/public/js/admin.min.js deleted file mode 100755 index b6ed01f4c..000000000 --- a/public/js/admin.min.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Pterodactyl - Panel - * Copyright (c) 2015 - 2016 Dane Everitt - * - * 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. - */ -function randomKey(length) { - var text = ''; - var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - for( var i=0; i < length; i++ ) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - - return text; -} -function escapeRegExp(str) { - return str.replace(/^\/|\/$/g, ''); -} -$(document).ready(function () { - $.urlParam=function(name){var results=new RegExp("[\\?&]"+name+"=([^&#]*)").exec(decodeURIComponent(window.location.href));if(results==null){return null}else{return results[1]||0}};function getPageName(url){var index=url.lastIndexOf("/")+1;var filenameWithExtension=url.substr(index);var filename=filenameWithExtension.split(".")[0];return filename} - function centerModal(element) { - var modal = (element.length > 0) ? element : $('.modal:visible'); - var clone = modal.clone().css('display', 'block').appendTo('body'); - var top = Math.round((clone.height() - clone.find('.modal-content').height()) / 3); - clone.remove(); - modal.find('div.modal-content').css('margin-top', top); - } - $('body').on('show.bs.modal', '.modal', function() { - centerModal($(this)); - }); - $(window).on('resize', centerModal); -}); diff --git a/public/js/autocomplete.js b/public/js/autocomplete.js new file mode 100644 index 000000000..15c43797e --- /dev/null +++ b/public/js/autocomplete.js @@ -0,0 +1,6 @@ +// Hacky fix for browsers ignoring autocomplete="off" +$(document).ready(function() { + $('.form-autocomplete-stop').on('click', function () { + $(this).removeAttr('readonly').blur().focus(); + }); +}); diff --git a/public/js/jquery.terminal-0.11.6.min.js b/public/js/jquery.terminal-0.11.6.min.js deleted file mode 100755 index c0e461d5e..000000000 --- a/public/js/jquery.terminal-0.11.6.min.js +++ /dev/null @@ -1,37 +0,0 @@ -/**@license - * __ _____ ________ __ - * / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / / - * __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ / - * / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__ - * \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/ - * \/ /____/ version 0.11.6 - * - * This file is part of jQuery Terminal. http://terminal.jcubic.pl - * - * Copyright (c) 2010-2016 Jakub Jankiewicz - * Released under the MIT license - * - * Contains: - * - * Storage plugin Distributed under the MIT License - * Copyright (c) 2010 Dave Schindler - * - * jQuery Timers licenced with the WTFPL - * - * - * Cross-Browser Split 1.1.1 - * Copyright 2007-2012 Steven Levithan - * Available under the MIT License - * - * jQuery Caret - * Copyright (c) 2009, Gideon Sireling - * 3 clause BSD License - * - * sprintf.js - * Copyright (c) 2007-2013 Alexandru Marasteanu - * licensed under 3 clause BSD license - * - * Date: Thu, 15 Sep 2016 20:19:15 +0000 - */ -(function(e){var n=function(){if(!n.cache.hasOwnProperty(arguments[0])){n.cache[arguments[0]]=n.parse(arguments[0])}return n.format.call(null,n.cache[arguments[0]],arguments)};n.format=function(e,r){var o=1,a=e.length,s="",l,f=[],c,u,h,p,m,g;for(c=0;c>>0;break;case"x":l=l.toString(16);break;case"X":l=l.toString(16).toUpperCase();break}l=/[def]/.test(h[8])&&h[3]&&l>=0?"+"+l:l;m=h[4]?h[4]=="0"?"0":h[4].charAt(1):" ";g=h[6]-String(l).length;p=h[6]?i(m,g):"";f.push(h[5]?l+p:p+l)}}return f.join("")};n.cache={};n.parse=function(e){var n=e,r=[],t=[],i=0;while(n){if((r=/^[^\x25]+/.exec(n))!==null){t.push(r[0])}else if((r=/^\x25{2}/.exec(n))!==null){t.push("%")}else if((r=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(n))!==null){if(r[2]){i|=1;var o=[],a=r[2],s=[];if((s=/^([a-z_][a-z_\d]*)/i.exec(a))!==null){o.push(s[1]);while((a=a.substring(s[0].length))!==""){if((s=/^\.([a-z_][a-z_\d]*)/i.exec(a))!==null){o.push(s[1])}else if((s=/^\[(\d+)\]/.exec(a))!==null){o.push(s[1])}else{throw"[sprintf] huh?"}}}else{throw"[sprintf] huh?"}r[2]=o}else{i|=2}if(i===3){throw"[sprintf] mixing positional and named placeholders is not (yet) supported"}t.push(r)}else{throw"[sprintf] huh?"}n=n.substring(r[0].length)}return t};var r=function(e,r,t){t=r.slice(0);t.splice(0,0,e);return n.apply(null,t)};function t(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}function i(e,n){for(var r=[];n>0;r[--n]=e){}return r.join("")}e.sprintf=n;e.vsprintf=r})(typeof global!="undefined"?global:window);(function(e,n){"use strict";e.omap=function(n,r){var t={};e.each(n,function(e,i){t[e]=r.call(n,e,i)});return t};var r={clone_object:function(n){var r={};if(typeof n=="object"){if(e.isArray(n)){return this.clone_array(n)}else if(n===null){return n}else{for(var t in n){if(e.isArray(n[t])){r[t]=this.clone_array(n[t])}else if(typeof n[t]=="object"){r[t]=this.clone_object(n[t])}else{r[t]=n[t]}}}}return r},clone_array:function(n){if(!e.isFunction(Array.prototype.map)){throw new Error("You'r browser don't support ES5 array map "+"use es5-shim")}return n.slice(0).map(function(e){if(typeof e=="object"){return this.clone_object(e)}else{return e}}.bind(this))}};var t=function(e){return r.clone_object(e)};var i=function(){var e="test",n=window.localStorage;try{n.setItem(e,"1");n.removeItem(e);return true}catch(r){return false}};var o=i();function a(e,n){var r;if(typeof e==="string"&&typeof n==="string"){localStorage[e]=n;return true}else if(typeof e==="object"&&typeof n==="undefined"){for(r in e){if(e.hasOwnProperty(r)){localStorage[r]=e[r]}}return true}return false}function s(e,n){var r,t,i;r=new Date;r.setTime(r.getTime()+31536e6);t="; expires="+r.toGMTString();if(typeof e==="string"&&typeof n==="string"){document.cookie=e+"="+n+t+"; path=/";return true}else if(typeof e==="object"&&typeof n==="undefined"){for(i in e){if(e.hasOwnProperty(i)){document.cookie=i+"="+e[i]+t+"; path=/"}}return true}return false}function l(e){return localStorage[e]}function f(e){var n,r,t,i;n=e+"=";r=document.cookie.split(";");for(t=0;ti&&i!==0||t.call(e,a)===false){h.timer.remove(e,r,t)}s.inProgress=false};s.$timerID=t.$timerID;if(!e.$timers[r][t.$timerID]){e.$timers[r][t.$timerID]=window.setInterval(s,n)}if(!this.global[r]){this.global[r]=[]}this.global[r].push(e)},remove:function(e,n,r){var t=e.$timers,i;if(t){if(!n){for(var o in t){if(t.hasOwnProperty(o)){this.remove(e,o,r)}}}else if(t[n]){if(r){if(r.$timerID){window.clearInterval(t[n][r.$timerID]);delete t[n][r.$timerID]}}else{for(var a in t[n]){if(t[n].hasOwnProperty(a)){window.clearInterval(t[n][a]);delete t[n][a]}}}for(i in t[n]){if(t[n].hasOwnProperty(i)){break}}if(!i){i=null;delete t[n]}}for(i in t){if(t.hasOwnProperty(i)){break}}if(!i){e.$timers=null}}}}});if(/(msie) ([\w.]+)/.exec(navigator.userAgent.toLowerCase())){h(window).one("unload",function(){var e=h.timer.global;for(var n in e){if(e.hasOwnProperty(n)){var r=e[n],t=r.length;while(--t){h.timer.remove(r[t],n)}}}})}(function(e){if(!String.prototype.split.toString().match(/\[native/)){return}var n=String.prototype.split,r=/()??/.exec("")[1]===e,t;t=function(t,i,o){if(Object.prototype.toString.call(i)!=="[object RegExp]"){return n.call(t,i,o)}var a=[],s=(i.ignoreCase?"i":"")+(i.multiline?"m":"")+(i.extended?"x":"")+(i.sticky?"y":""),l=0,f,c,u,h;i=new RegExp(i.source,s+"g");t+="";if(!r){f=new RegExp("^"+i.source+"$(?!\\s)",s)}o=o===e?-1>>>0:o>>>0;while(c=i.exec(t)){u=c.index+c[0].length;if(u>l){a.push(t.slice(l,c.index));if(!r&&c.length>1){c[0].replace(f,function(){for(var n=1;n1&&c.index=o){break}}if(i.lastIndex===c.index){i.lastIndex++}}if(l===t.length){if(h||!i.test("")){a.push("")}}else{a.push(t.slice(l))}return a.length>o?a.slice(0,o):a};String.prototype.split=function(e,n){return t(this,e,n)};return t})();e.fn.caret=function(e){var n=this[0];var r=n.contentEditable==="true";if(arguments.length==0){if(window.getSelection){if(r){n.focus();var t=window.getSelection().getRangeAt(0),i=t.cloneRange();i.selectNodeContents(n);i.setEnd(t.endContainer,t.endOffset);return i.toString().length}return n.selectionStart}if(document.selection){n.focus();if(r){var t=document.selection.createRange(),i=document.body.createTextRange();i.moveToElementText(n);i.setEndPoint("EndToEnd",t);return i.text.length}var e=0,o=n.createTextRange(),i=document.selection.createRange().duplicate(),a=i.getBookmark();o.moveToBookmark(a);while(o.moveStart("character",-1)!==0)e++;return e}return 0}if(e==-1)e=this[r?"text":"val"]().length;if(window.getSelection){if(r){n.focus();window.getSelection().collapse(n.firstChild,e)}else n.setSelectionRange(e,e)}else if(document.body.createTextRange){var o=document.body.createTextRange();o.moveToElementText(n);o.moveStart("character",e);o.collapse(true);o.select()}if(!r)n.focus();return e};function p(e,n){var r=[];var t=e.length;if(tr.length){if(n){break}e=0;n=true}}return r[e]}},append:function(e){r.push(e)}})}function g(n){var r=n instanceof Array?n:n?[n]:[];e.extend(this,{data:function(){return r},map:function(n){return e.map(r,n)},size:function(){return r.length},pop:function(){if(r.length===0){return null}else{var e=r[r.length-1];r=r.slice(0,r.length-1);return e}},push:function(e){r=r.concat([e]);return e},top:function(){return r.length>0?r[r.length-1]:null},clone:function(){return new g(r.slice(0))}})}e.json_stringify=function(r,t){var i="",o;t=t===n?1:t;var a=typeof r;switch(a){case"function":i+=r;break;case"boolean":i+=r?"true":"false";break;case"object":if(r===null){i+="null"}else if(r instanceof Array){i+="[";var s=r.length;for(o=0;o1?",":"";if(t===1){i=i.replace(/,([\]}])/g,"$1")}return i.replace(/([\[{]),/g,"$1")};function d(n,r){var t=true;var i="";if(typeof n==="string"&&n!==""){i=n+"_"}i+="commands";var o=e.Storage.get(i);o=o?e.parseJSON(o):[];var a=o.length-1;e.extend(this,{append:function(n){if(t){if(o[o.length-1]!==n){o.push(n);if(r&&o.length>r){o=o.slice(-r)}a=o.length-1;e.Storage.set(i,e.json_stringify(o))}}},data:function(){return o},reset:function(){a=o.length-1},last:function(){return o[length-1]},end:function(){return a===o.length-1},position:function(){return a},current:function(){return o[a]},next:function(){if(a0){--a}if(e!==-1){return o[a]}},clear:function(){o=[];this.purge()},enabled:function(){return t},enable:function(){t=true},purge:function(){e.Storage.remove(i)},disable:function(){t=false}})}var v=function(){var e=document.createElement("div");e.setAttribute("onpaste","return;");return typeof e.onpaste=="function"}();var y=true;e.fn.cmd=function(r){var t=this;var i=t.data("cmd");if(i){return i}t.addClass("cmd");t.append(''+' ');var o=e("${6}\nsnippet tfoot\n \n ${1}\n \nsnippet th\n ${1}\nsnippet th.\n ${2}\nsnippet th#\n ${2}\nsnippet th+\n ${1}\n th+${2}\nsnippet thead\n \n ${1}\n \nsnippet time\n