GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — develop ( 31864d...b71604 )
by Dane
02:33
created

ServerRepository::generateSFTPUsername()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 11
nc 6
nop 2
1
<?php
2
/**
3
 * Pterodactyl - Panel
4
 * Copyright (c) 2015 - 2016 Dane Everitt <[email protected]>.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
namespace Pterodactyl\Repositories;
26
27
use DB;
28
use Log;
29
use Crypt;
30
use Validator;
31
use Pterodactyl\Models;
32
use Pterodactyl\Events\ServerDeleted;
33
use Pterodactyl\Services\UuidService;
34
use Pterodactyl\Services\DeploymentService;
35
use Pterodactyl\Exceptions\DisplayException;
36
use Pterodactyl\Notifications\ServerCreated;
37
use Pterodactyl\Exceptions\DisplayValidationException;
38
39
class ServerRepository
40
{
41
    protected $daemonPermissions = [
42
        's:*',
43
    ];
44
45
    public function __construct()
46
    {
47
        //
48
    }
49
50
    /**
51
     * Generates a SFTP username for a server given a server name.
52
     * format: mumble_67c7a4b0.
53
     *
54
     * @param  string $name
55
     * @param  string $identifier
56
     * @return string
57
     */
58
    protected function generateSFTPUsername($name, $identifier = null)
59
    {
60
        if (is_null($identifier) || ! ctype_alnum($identifier)) {
61
            $unique = str_random(8);
62
        } else {
63
            if (strlen($identifier) < 8) {
64
                $unique = $identifier . str_random((8 - strlen($identifier)));
65
            } else {
66
                $unique = substr($identifier, 0, 8);
67
            }
68
        }
69
70
        // Filter the Server Name
71
        $name = trim(preg_replace('/[^\w]+/', '', $name), '_');
72
        $name = (strlen($name) < 1) ? str_random(6) : $name;
73
74
        return strtolower(substr($name, 0, 6) . '_' . $unique);
75
    }
76
77
    /**
78
     * Adds a new server to the system.
79
     * @param   array  $data  An array of data descriptors for creating the server. These should align to the columns in the database.
80
     * @return  int
81
     */
82
    public function create(array $data)
83
    {
84
85
        // Validate Fields
86
        $validator = Validator::make($data, [
87
            'owner' => 'bail|required',
88
            'name' => 'required|regex:/^([\w .-]{1,200})$/',
89
            'memory' => 'required|numeric|min:0',
90
            'swap' => 'required|numeric|min:-1',
91
            'io' => 'required|numeric|min:10|max:1000',
92
            'cpu' => 'required|numeric|min:0',
93
            'disk' => 'required|numeric|min:0',
94
            'service' => 'required|numeric|min:1|exists:services,id',
95
            'option' => 'required|numeric|min:1|exists:service_options,id',
96
            'pack' => 'required|numeric|min:0',
97
            'startup' => 'string',
98
            'custom_image_name' => 'required_if:use_custom_image,on',
99
            'auto_deploy' => 'sometimes|boolean',
100
            'custom_id' => 'sometimes|required|numeric|unique:servers,id',
101
        ]);
102
103
        $validator->sometimes('node', 'bail|required|numeric|min:1|exists:nodes,id', function ($input) {
104
            return ! ($input->auto_deploy);
105
        });
106
107
        $validator->sometimes('ip', 'required|ip', function ($input) {
108
            return ! $input->auto_deploy && ! $input->allocation;
109
        });
110
111
        $validator->sometimes('port', 'required|numeric|min:1|max:65535', function ($input) {
112
            return ! $input->auto_deploy && ! $input->allocation;
113
        });
114
115
        $validator->sometimes('allocation', 'numeric|exists:allocations,id', function ($input) {
116
            return ! ($input->auto_deploy || ($input->port && $input->ip));
117
        });
118
119
        // Run validator, throw catchable and displayable exception if it fails.
120
        // Exception includes a JSON result of failed validation rules.
121
        if ($validator->fails()) {
122
            throw new DisplayValidationException($validator->errors());
123
        }
124
125
        if (is_int($data['owner'])) {
126
            $user = Models\User::select('id', 'email')->where('id', $data['owner'])->first();
127
        } else {
128
            $user = Models\User::select('id', 'email')->where('email', $data['owner'])->first();
129
        }
130
131
        if (! $user) {
132
            throw new DisplayException('The user id or email passed to the function was not found on the system.');
133
        }
134
135
        $autoDeployed = false;
136
        if (isset($data['auto_deploy']) && in_array($data['auto_deploy'], [true, 1, '1'])) {
137
            // This is an auto-deployment situation
138
            // Ignore any other passed node data
139
            unset($data['node'], $data['ip'], $data['port'], $data['allocation']);
140
141
            $autoDeployed = true;
142
            $node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location']);
143
            $allocation = DeploymentService::randomAllocation($node->id);
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Node>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
144
        } else {
145
            $node = Models\Node::getByID($data['node']);
146
        }
147
148
        // Verify IP & Port are a.) free and b.) assigned to the node.
149
        // We know the node exists because of 'exists:nodes,id' in the validation
150
        if (! $autoDeployed) {
151
            if (! isset($data['allocation'])) {
152
                $allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->where('node', $data['node'])->whereNull('assigned_to')->first();
153
            } else {
154
                $allocation = Models\Allocation::where('id', $data['allocation'])->where('node', $data['node'])->whereNull('assigned_to')->first();
155
            }
156
        }
157
158
        // Something failed in the query, either that combo doesn't exist, or it is in use.
159
        if (! $allocation) {
0 ignored issues
show
Bug introduced by
The variable $allocation does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
160
            throw new DisplayException('The selected IP/Port combination or Allocation ID is either already in use, or unavaliable for this node.');
161
        }
162
163
        // Validate those Service Option Variables
164
        // We know the service and option exists because of the validation.
165
        // We need to verify that the option exists for the service, and then check for
166
        // any required variable fields. (fields are labeled env_<env_variable>)
167
        $option = Models\ServiceOptions::where('id', $data['option'])->where('parent_service', $data['service'])->first();
168
        if (! $option) {
169
            throw new DisplayException('The requested service option does not exist for the specified service.');
170
        }
171
172
        // Validate the Pack
173
        if ($data['pack'] == 0) {
174
            $data['pack'] = null;
175
        }
176
177
        if (! is_null($data['pack'])) {
178
            $pack = Models\ServicePack::where('id', $data['pack'])->where('option', $data['option'])->first();
179
            if (! $pack) {
180
                throw new DisplayException('The requested service pack does not seem to exist for this combination.');
181
            }
182
        }
183
184
        // Load up the Service Information
185
        $service = Models\Service::find($option->parent_service);
186
187
        // Check those Variables
188
        $variables = Models\ServiceVariables::where('option_id', $data['option'])->get();
189
        $variableList = [];
190
        if ($variables) {
191
            foreach ($variables as $variable) {
192
193
                // Is the variable required?
194
                if (! isset($data['env_' . $variable->env_variable])) {
195
                    if ($variable->required === 1) {
196
                        throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.');
197
                    }
198
                    $variableList[] = [
199
                        'id' => $variable->id,
200
                        'env' => $variable->env_variable,
201
                        'val' => $variable->default_value,
202
                    ];
203
                    continue;
204
                }
205
206
                // Check aganist Regex Pattern
207 View Code Duplication
                if (! is_null($variable->regex) && ! preg_match($variable->regex, $data['env_' . $variable->env_variable])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
208
                    throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').');
209
                }
210
211
                $variableList[] = [
212
                    'id' => $variable->id,
213
                    'env' => $variable->env_variable,
214
                    'val' => $data['env_' . $variable->env_variable],
215
                ];
216
                continue;
217
            }
218
        }
219
220
        // Check Overallocation
221
        if (! $autoDeployed) {
222
            if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) {
223
                $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first();
224
225
                // Check memory limits
226 View Code Duplication
                if (is_numeric($node->memory_overallocate)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
                    $newMemory = $totals->memory + $data['memory'];
228
                    $memoryLimit = ($node->memory * (1 + ($node->memory_overallocate / 100)));
229
                    if ($newMemory > $memoryLimit) {
230
                        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.');
231
                    }
232
                }
233
234
                // Check Disk Limits
235 View Code Duplication
                if (is_numeric($node->disk_overallocate)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
                    $newDisk = $totals->disk + $data['disk'];
237
                    $diskLimit = ($node->disk * (1 + ($node->disk_overallocate / 100)));
238
                    if ($newDisk > $diskLimit) {
239
                        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.');
240
                    }
241
                }
242
            }
243
        }
244
245
        DB::beginTransaction();
246
247
        try {
248
            $uuid = new UuidService;
249
250
            // Add Server to the Database
251
            $server = new Models\Server;
252
            $genUuid = $uuid->generate('servers', 'uuid');
253
            $genShortUuid = $uuid->generateShort('servers', 'uuidShort', $genUuid);
254
255
            if (isset($data['custom_id'])) {
256
                $server->id = $data['custom_id'];
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Server>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
257
            }
258
259
            $server->fill([
260
                'uuid' => $genUuid,
261
                'uuidShort' => $genShortUuid,
262
                'node' => $node->id,
263
                'name' => $data['name'],
264
                'suspended' => 0,
265
                'owner' => $user->id,
266
                'memory' => $data['memory'],
267
                'swap' => $data['swap'],
268
                'disk' => $data['disk'],
269
                'io' => $data['io'],
270
                'cpu' => $data['cpu'],
271
                'oom_disabled' => (isset($data['oom_disabled'])) ? true : false,
272
                'allocation' => $allocation->id,
273
                'service' => $data['service'],
274
                'option' => $data['option'],
275
                'pack' => $data['pack'],
276
                'startup' => $data['startup'],
277
                'daemonSecret' => $uuid->generate('servers', 'daemonSecret'),
278
                'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image,
279
                'username' => $this->generateSFTPUsername($data['name'], $genShortUuid),
280
                'sftp_password' => Crypt::encrypt('not set'),
281
            ]);
282
            $server->save();
283
284
            // Mark Allocation in Use
285
            $allocation->assigned_to = $server->id;
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Server>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
286
            $allocation->save();
287
288
            // Add Variables
289
            $environmentVariables = [
290
                'STARTUP' => $data['startup'],
291
            ];
292
293
            foreach ($variableList as $item) {
294
                $environmentVariables[$item['env']] = $item['val'];
295
296
                Models\ServerVariables::create([
297
                    'server_id' => $server->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
298
                    'variable_id' => $item['id'],
299
                    'variable_value' => $item['val'],
300
                ]);
301
            }
302
303
            // Queue Notification Email
304
            $user->notify((new ServerCreated([
305
                'name' => $server->name,
0 ignored issues
show
Documentation introduced by
The property name does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
306
                'memory' => $server->memory,
0 ignored issues
show
Documentation introduced by
The property memory does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
307
                'node' => $node->name,
308
                'service' => $service->name,
309
                'option' => $option->name,
310
                'uuidShort' => $server->uuidShort,
0 ignored issues
show
Documentation introduced by
The property uuidShort does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
311
            ])));
312
313
            $client = Models\Node::guzzleRequest($node->id);
314
            $client->request('POST', '/servers', [
315
                'headers' => [
316
                    'X-Access-Token' => $node->daemonSecret,
317
                ],
318
                'json' => [
319
                    'uuid' => (string) $server->uuid,
0 ignored issues
show
Documentation introduced by
The property uuid does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
320
                    'user' => $server->username,
0 ignored issues
show
Documentation introduced by
The property username does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
321
                    'build' => [
322
                        'default' => [
323
                            'ip' => $allocation->ip,
324
                            'port' => (int) $allocation->port,
325
                        ],
326
                        'ports' => [
327
                            (string) $allocation->ip => [(int) $allocation->port],
328
                        ],
329
                        'env' => $environmentVariables,
330
                        'memory' => (int) $server->memory,
0 ignored issues
show
Documentation introduced by
The property memory does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
331
                        'swap' => (int) $server->swap,
0 ignored issues
show
Documentation introduced by
The property swap does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
332
                        'io' => (int) $server->io,
0 ignored issues
show
Documentation introduced by
The property io does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
333
                        'cpu' => (int) $server->cpu,
0 ignored issues
show
Documentation introduced by
The property cpu does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
334
                        'disk' => (int) $server->disk,
0 ignored issues
show
Documentation introduced by
The property disk does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
335
                        'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image,
336
                    ],
337
                    'service' => [
338
                        'type' => $service->file,
339
                        'option' => $option->tag,
340
                        'pack' => (isset($pack)) ? $pack->uuid : null,
341
                    ],
342
                    'keys' => [
343
                        (string) $server->daemonSecret => $this->daemonPermissions,
0 ignored issues
show
Documentation introduced by
The property daemonSecret does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
344
                    ],
345
                    'rebuild' => false,
346
                ],
347
            ]);
348
349
            DB::commit();
350
351
            return $server->id;
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
352
        } catch (\GuzzleHttp\Exception\TransferException $ex) {
353
            DB::rollBack();
354
            throw new DisplayException('There was an error while attempting to connect to the daemon to add this server.', $ex);
355
        } catch (\Exception $ex) {
356
            DB::rollBack();
357
            throw $ex;
358
        }
359
    }
360
361
    /**
362
     * [updateDetails description].
363
     * @param  int  $id
364
     * @param  array    $data
365
     * @return bool
366
     */
367
    public function updateDetails($id, array $data)
368
    {
369
        $uuid = new UuidService;
370
        $resetDaemonKey = false;
371
372
        // Validate Fields
373
        $validator = Validator::make($data, [
374
            'owner' => 'email|exists:users,email',
375
            'name' => 'regex:([\w .-]{1,200})',
376
        ]);
377
378
        // Run validator, throw catchable and displayable exception if it fails.
379
        // Exception includes a JSON result of failed validation rules.
380
        if ($validator->fails()) {
381
            throw new DisplayValidationException($validator->errors());
382
        }
383
384
        DB::beginTransaction();
385
386
        try {
387
            $server = Models\Server::findOrFail($id);
388
            $owner = Models\User::findOrFail($server->owner);
389
390
            // Update daemon secret if it was passed.
391
            if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $owner->email)) {
392
                $oldDaemonKey = $server->daemonSecret;
393
                $server->daemonSecret = $uuid->generate('servers', 'daemonSecret');
394
                $resetDaemonKey = true;
395
            }
396
397
            // Update Server Owner if it was passed.
398
            if (isset($data['owner']) && $data['owner'] !== $owner->email) {
399
                $newOwner = Models\User::select('id')->where('email', $data['owner'])->first();
400
                $server->owner = $newOwner->id;
401
            }
402
403
            // Update Server Name if it was passed.
404
            if (isset($data['name'])) {
405
                $server->name = $data['name'];
406
            }
407
408
            // Save our changes
409
            $server->save();
410
411
            // Do we need to update? If not, return successful.
412
            if (! $resetDaemonKey) {
413
                DB::commit();
414
415
                return true;
416
            }
417
418
            // If we need to update do it here.
419
            $node = Models\Node::getByID($server->node);
420
            $client = Models\Node::guzzleRequest($server->node);
421
422
            $res = $client->request('PATCH', '/server', [
423
                'headers' => [
424
                    'X-Access-Server' => $server->uuid,
425
                    'X-Access-Token' => $node->daemonSecret,
426
                ],
427
                'exceptions' => false,
428
                'json' => [
429
                    'keys' => [
430
                        (string) $oldDaemonKey => [],
0 ignored issues
show
Bug introduced by
The variable $oldDaemonKey does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
431
                        (string) $server->daemonSecret => $this->daemonPermissions,
432
                    ],
433
                ],
434
            ]);
435
436
            if ($res->getStatusCode() === 204) {
437
                DB::commit();
438
439
                return true;
440
            } else {
441
                throw new DisplayException('Daemon returned a a non HTTP/204 error code. HTTP/' + $res->getStatusCode());
442
            }
443
        } catch (\Exception $ex) {
444
            DB::rollBack();
445
            Log::error($ex);
446
            throw new DisplayException('An error occured while attempting to update this server\'s information.');
447
        }
448
    }
449
450
    /**
451
     * [updateContainer description].
452
     * @param  int      $id
453
     * @param  array    $data
454
     * @return bool
455
     */
456 View Code Duplication
    public function updateContainer($id, array $data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
457
    {
458
        $validator = Validator::make($data, [
459
            'image' => 'required|string',
460
        ]);
461
462
        // Run validator, throw catchable and displayable exception if it fails.
463
        // Exception includes a JSON result of failed validation rules.
464
        if ($validator->fails()) {
465
            throw new DisplayValidationException($validator->errors());
466
        }
467
468
        DB::beginTransaction();
469
        try {
470
            $server = Models\Server::findOrFail($id);
471
472
            $server->image = $data['image'];
473
            $server->save();
474
475
            $node = Models\Node::getByID($server->node);
476
            $client = Models\Node::guzzleRequest($server->node);
477
478
            $client->request('PATCH', '/server', [
479
                'headers' => [
480
                    'X-Access-Server' => $server->uuid,
481
                    'X-Access-Token' => $node->daemonSecret,
482
                ],
483
                'json' => [
484
                    'build' => [
485
                        'image' => $server->image,
486
                    ],
487
                ],
488
            ]);
489
490
            DB::commit();
491
492
            return true;
493
        } catch (\GuzzleHttp\Exception\TransferException $ex) {
494
            DB::rollBack();
495
            throw new DisplayException('An error occured while attempting to update the container image.', $ex);
496
        } catch (\Exception $ex) {
497
            DB::rollBack();
498
            throw $ex;
499
        }
500
    }
501
502
    /**
503
     * [changeBuild description].
504
     * @param  int  $id
505
     * @param  array    $data
506
     * @return bool
507
     */
508
    public function changeBuild($id, array $data)
509
    {
510
        $validator = Validator::make($data, [
511
            'default' => [
512
                'string',
513
                '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})$/',
514
            ],
515
            'add_additional' => 'nullable|array',
516
            'remove_additional' => 'nullable|array',
517
            'memory' => 'integer|min:0',
518
            'swap' => 'integer|min:-1',
519
            'io' => 'integer|min:10|max:1000',
520
            'cpu' => 'integer|min:0',
521
            'disk' => 'integer|min:0',
522
        ]);
523
524
        // Run validator, throw catchable and displayable exception if it fails.
525
        // Exception includes a JSON result of failed validation rules.
526
        if ($validator->fails()) {
527
            throw new DisplayValidationException($validator->errors());
528
        }
529
530
        DB::beginTransaction();
531
532
        try {
533
            $server = Models\Server::findOrFail($id);
534
            $allocation = Models\Allocation::findOrFail($server->allocation);
535
536
            $newBuild = [];
537
538
            if (isset($data['default'])) {
539
                list($ip, $port) = explode(':', $data['default']);
540
                if ($ip !== $allocation->ip || (int) $port !== $allocation->port) {
541
                    $selection = Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->first();
542
                    if (! $selection) {
543
                        throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.');
544
                    }
545
546
                    $server->allocation = $selection->id;
547
                    $newBuild['default'] = [
548
                        'ip' => $ip,
549
                        'port' => (int) $port,
550
                    ];
551
552
                    // Re-Run to keep updated for rest of function
553
                    $allocation = Models\Allocation::findOrFail($server->allocation);
554
                }
555
            }
556
557
            $newPorts = false;
558
            // Remove Assignments
559
            if (isset($data['remove_additional'])) {
560
                foreach ($data['remove_additional'] as $id => $combo) {
561
                    list($ip, $port) = explode(':', $combo);
562
                    // Invalid, not worth killing the whole thing, we'll just skip over it.
563
                    if (! filter_var($ip, FILTER_VALIDATE_IP) || ! preg_match('/^(\d{1,5})$/', $port)) {
564
                        break;
565
                    }
566
567
                    // Can't remove the assigned IP/Port combo
568
                    if ($ip === $allocation->ip && (int) $port === (int) $allocation->port) {
569
                        break;
570
                    }
571
572
                    $newPorts = true;
573
                    Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->update([
574
                        'assigned_to' => null,
575
                    ]);
576
                }
577
            }
578
579
            // Add Assignments
580
            if (isset($data['add_additional'])) {
581
                foreach ($data['add_additional'] as $id => $combo) {
582
                    list($ip, $port) = explode(':', $combo);
583
                    // Invalid, not worth killing the whole thing, we'll just skip over it.
584
                    if (! filter_var($ip, FILTER_VALIDATE_IP) || ! preg_match('/^(\d{1,5})$/', $port)) {
585
                        break;
586
                    }
587
588
                    // Don't allow double port assignments
589
                    if (Models\Allocation::where('port', $port)->where('assigned_to', $server->id)->count() !== 0) {
590
                        break;
591
                    }
592
593
                    $newPorts = true;
594
                    Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('assigned_to')->update([
595
                        'assigned_to' => $server->id,
596
                    ]);
597
                }
598
            }
599
600
            // Loop All Assignments
601
            $additionalAssignments = [];
602
            $assignments = Models\Allocation::where('assigned_to', $server->id)->get();
603
            foreach ($assignments as &$assignment) {
604
                if (array_key_exists((string) $assignment->ip, $additionalAssignments)) {
605
                    array_push($additionalAssignments[(string) $assignment->ip], (int) $assignment->port);
606
                } else {
607
                    $additionalAssignments[(string) $assignment->ip] = [(int) $assignment->port];
608
                }
609
            }
610
611
            if ($newPorts === true) {
612
                $newBuild['ports|overwrite'] = $additionalAssignments;
613
            }
614
615
            // @TODO: verify that server can be set to this much memory without
616
            // going over node limits.
617 View Code Duplication
            if (isset($data['memory']) && $server->memory !== (int) $data['memory']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
618
                $server->memory = $data['memory'];
619
                $newBuild['memory'] = (int) $server->memory;
620
            }
621
622 View Code Duplication
            if (isset($data['swap']) && $server->swap !== (int) $data['swap']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
623
                $server->swap = $data['swap'];
624
                $newBuild['swap'] = (int) $server->swap;
625
            }
626
627
            // @TODO: verify that server can be set to this much disk without
628
            // going over node limits.
629 View Code Duplication
            if (isset($data['disk']) && $server->disk !== (int) $data['disk']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
630
                $server->disk = $data['disk'];
631
                $newBuild['disk'] = (int) $server->disk;
632
            }
633
634 View Code Duplication
            if (isset($data['cpu']) && $server->cpu !== (int) $data['cpu']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
635
                $server->cpu = $data['cpu'];
636
                $newBuild['cpu'] = (int) $server->cpu;
637
            }
638
639 View Code Duplication
            if (isset($data['io']) && $server->io !== (int) $data['io']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
640
                $server->io = $data['io'];
641
                $newBuild['io'] = (int) $server->io;
642
            }
643
644
            // Try save() here so if it fails we haven't contacted the daemon
645
            // This won't be committed unless the HTTP request succeedes anyways
646
            $server->save();
647
648
            if (! empty($newBuild)) {
649
                $node = Models\Node::getByID($server->node);
650
                $client = Models\Node::guzzleRequest($server->node);
651
652
                $client->request('PATCH', '/server', [
653
                    'headers' => [
654
                        'X-Access-Server' => $server->uuid,
655
                        'X-Access-Token' => $node->daemonSecret,
656
                    ],
657
                    'json' => [
658
                        'build' => $newBuild,
659
                    ],
660
                ]);
661
            }
662
663
            DB::commit();
664
665
            return true;
666
        } catch (\GuzzleHttp\Exception\TransferException $ex) {
667
            DB::rollBack();
668
            throw new DisplayException('An error occured while attempting to update the configuration.', $ex);
669
        } catch (\Exception $ex) {
670
            DB::rollBack();
671
            throw $ex;
672
        }
673
    }
674
675
    public function updateStartup($id, array $data, $admin = false)
676
    {
677
        $server = Models\Server::findOrFail($id);
678
679
        DB::beginTransaction();
680
681
        try {
682
            // Check the startup
683
            if (isset($data['startup'])) {
684
                $server->startup = $data['startup'];
685
                $server->save();
686
            }
687
688
            // Check those Variables
689
            $variables = Models\ServiceVariables::select(
690
                    'service_variables.*',
691
                    DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_currentValue')
692
                )->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id')
693
                ->where('option_id', $server->option)
694
                ->get();
695
696
            $variableList = [];
697
            if ($variables) {
698
                foreach ($variables as &$variable) {
699
                    // Move on if the new data wasn't even sent
700 View Code Duplication
                    if (! isset($data[$variable->env_variable])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
701
                        $variableList[] = [
702
                            'id' => $variable->id,
703
                            'env' => $variable->env_variable,
704
                            'val' => $variable->a_currentValue,
705
                        ];
706
                        continue;
707
                    }
708
709
                    // Update Empty but skip validation
710 View Code Duplication
                    if (empty($data[$variable->env_variable])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
711
                        $variableList[] = [
712
                            'id' => $variable->id,
713
                            'env' => $variable->env_variable,
714
                            'val' => null,
715
                        ];
716
                        continue;
717
                    }
718
719
                    // Is the variable required?
720
                    // @TODO: is this even logical to perform this check?
721
                    if (isset($data[$variable->env_variable]) && empty($data[$variable->env_variable])) {
722
                        if ($variable->required === 1) {
723
                            throw new DisplayException('A required service option variable field (' . $variable->env_variable . ') was included in this request but was left blank.');
724
                        }
725
                    }
726
727
                    // Variable hidden and/or not user editable
728
                    if (($variable->user_viewable === 0 || $variable->user_editable === 0) && ! $admin) {
729
                        throw new DisplayException('A service option variable field (' . $variable->env_variable . ') does not exist or you do not have permission to edit it.');
730
                    }
731
732
                    // Check aganist Regex Pattern
733 View Code Duplication
                    if (! is_null($variable->regex) && ! preg_match($variable->regex, $data[$variable->env_variable])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
734
                        throw new DisplayException('Failed to validate service option variable field (' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').');
735
                    }
736
737
                    $variableList[] = [
738
                        'id' => $variable->id,
739
                        'env' => $variable->env_variable,
740
                        'val' => $data[$variable->env_variable],
741
                    ];
742
                }
743
            }
744
745
            // Add Variables
746
            $environmentVariables = [
747
                'STARTUP' => $server->startup,
748
            ];
749
            foreach ($variableList as $item) {
750
                $environmentVariables[$item['env']] = $item['val'];
751
752
                // Update model or make a new record if it doesn't exist.
753
                $model = Models\ServerVariables::firstOrNew([
754
                    'variable_id' => $item['id'],
755
                    'server_id' => $server->id,
756
                ]);
757
                $model->variable_value = $item['val'];
758
                $model->save();
759
            }
760
761
            $node = Models\Node::getByID($server->node);
762
            $client = Models\Node::guzzleRequest($server->node);
763
764
            $client->request('PATCH', '/server', [
765
                'headers' => [
766
                    'X-Access-Server' => $server->uuid,
767
                    'X-Access-Token' => $node->daemonSecret,
768
                ],
769
                'json' => [
770
                    'build' => [
771
                        'env|overwrite' => $environmentVariables,
772
                    ],
773
                ],
774
            ]);
775
776
            DB::commit();
777
778
            return true;
779
        } catch (\GuzzleHttp\Exception\TransferException $ex) {
780
            DB::rollBack();
781
            throw new DisplayException('An error occured while attempting to update the server configuration.', $ex);
782
        } catch (\Exception $ex) {
783
            DB::rollBack();
784
            throw $ex;
785
        }
786
    }
787
788
    public function deleteServer($id, $force)
789
    {
790
        $server = Models\Server::findOrFail($id);
791
        DB::beginTransaction();
792
793
        try {
794
            if ($force === 'force' || $force === true) {
795
                $server->installed = 3;
796
                $server->save();
797
            }
798
799
            $server->delete();
800
            DB::commit();
801
802
            event(new ServerDeleted($server->id));
803
        } catch (\Exception $ex) {
804
            DB::rollBack();
805
            throw $ex;
806
        }
807
    }
808
809
    public function deleteNow($id, $force = false)
810
    {
811
        $server = Models\Server::withTrashed()->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method withTrashed() does not exist on Pterodactyl\Models\Server. Did you maybe mean trashed()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
812
        $node = Models\Node::findOrFail($server->node);
813
814
        // Handle server being restored previously or
815
        // an accidental queue.
816
        if (! $server->trashed()) {
817
            return;
818
        }
819
820
        DB::beginTransaction();
821
        try {
822
            // Unassign Allocations
823
            Models\Allocation::where('assigned_to', $server->id)->update([
824
                'assigned_to' => null,
825
            ]);
826
827
            // Remove Variables
828
            Models\ServerVariables::where('server_id', $server->id)->delete();
829
830
            // Remove Permissions (Foreign Key requires before Subusers)
831
            Models\Permission::where('server_id', $server->id)->delete();
832
833
            // Remove SubUsers
834
            Models\Subuser::where('server_id', $server->id)->delete();
835
836
            // Remove Downloads
837
            Models\Download::where('server', $server->uuid)->delete();
838
839
            // Clear Tasks
840
            Models\Task::where('server', $server->id)->delete();
841
842
            // Delete Databases
843
            // This is the one un-recoverable point where
844
            // transactions will not save us.
845
            $repository = new DatabaseRepository;
846
            foreach (Models\Database::select('id')->where('server_id', $server->id)->get() as &$database) {
0 ignored issues
show
Bug introduced by
The expression \Pterodactyl\Models\Data...d', $server->id)->get() cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
847
                $repository->drop($database->id);
848
            }
849
850
            $client = Models\Node::guzzleRequest($server->node);
851
            $client->request('DELETE', '/servers', [
852
                'headers' => [
853
                    'X-Access-Token' => $node->daemonSecret,
854
                    'X-Access-Server' => $server->uuid,
855
                ],
856
            ]);
857
858
            $server->forceDelete();
859
            DB::commit();
860
        } catch (\GuzzleHttp\Exception\TransferException $ex) {
861
            // Set installed is set to 3 when force deleting.
862
            if ($server->installed === 3 || $force) {
863
                $server->forceDelete();
864
                DB::commit();
865
            } else {
866
                DB::rollBack();
867
                throw $ex;
868
            }
869
        } catch (\Exception $ex) {
870
            DB::rollBack();
871
            throw $ex;
872
        }
873
    }
874
875
    public function cancelDeletion($id)
876
    {
877
        $server = Models\Server::withTrashed()->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method withTrashed() does not exist on Pterodactyl\Models\Server. Did you maybe mean trashed()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
878
        $server->restore();
879
880
        $server->installed = 1;
881
        $server->save();
882
    }
883
884
    public function toggleInstall($id)
885
    {
886
        $server = Models\Server::findOrFail($id);
887
        if ($server->installed === 2) {
888
            throw new DisplayException('This server was marked as having a failed install, you cannot override this.');
889
        }
890
        $server->installed = ($server->installed === 1) ? 0 : 1;
891
892
        return $server->save();
893
    }
894
895
    /**
896
     * Suspends a server instance making it unable to be booted or used by a user.
897
     * @param  int $id
898
     * @return bool
899
     */
900
    public function suspend($id, $deleted = false)
901
    {
902
        $server = ($deleted) ? Models\Server::withTrashed()->findOrFail($id) : Models\Server::findOrFail($id);
0 ignored issues
show
Bug introduced by
The method withTrashed() does not exist on Pterodactyl\Models\Server. Did you maybe mean trashed()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
903
        $node = Models\Node::findOrFail($server->node);
904
905
        DB::beginTransaction();
906
907
        try {
908
909
            // Already suspended, no need to make more requests.
910
            if ($server->suspended === 1) {
911
                return true;
912
            }
913
914
            $server->suspended = 1;
915
            $server->save();
916
917
            $client = Models\Node::guzzleRequest($server->node);
918
            $client->request('POST', '/server/suspend', [
919
                'headers' => [
920
                    'X-Access-Token' => $node->daemonSecret,
921
                    'X-Access-Server' => $server->uuid,
922
                ],
923
            ]);
924
925
            return DB::commit();
926
        } catch (\GuzzleHttp\Exception\TransferException $ex) {
927
            DB::rollBack();
928
            throw new DisplayException('An error occured while attempting to contact the remote daemon to suspend this server.', $ex);
929
        } catch (\Exception $ex) {
930
            DB::rollBack();
931
            throw $ex;
932
        }
933
    }
934
935
    /**
936
     * Unsuspends a server instance.
937
     * @param  int $id
938
     * @return bool
939
     */
940
    public function unsuspend($id)
941
    {
942
        $server = Models\Server::findOrFail($id);
943
        $node = Models\Node::findOrFail($server->node);
944
945
        DB::beginTransaction();
946
947
        try {
948
949
            // Already unsuspended, no need to make more requests.
950
            if ($server->suspended === 0) {
951
                return true;
952
            }
953
954
            $server->suspended = 0;
955
            $server->save();
956
957
            $client = Models\Node::guzzleRequest($server->node);
958
            $client->request('POST', '/server/unsuspend', [
959
                'headers' => [
960
                    'X-Access-Token' => $node->daemonSecret,
961
                    'X-Access-Server' => $server->uuid,
962
                ],
963
            ]);
964
965
            return DB::commit();
966
        } catch (\GuzzleHttp\Exception\TransferException $ex) {
967
            DB::rollBack();
968
            throw new DisplayException('An error occured while attempting to contact the remote daemon to un-suspend this server.', $ex);
969
        } catch (\Exception $ex) {
970
            DB::rollBack();
971
            throw $ex;
972
        }
973
    }
974
975 View Code Duplication
    public function updateSFTPPassword($id, $password)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
976
    {
977
        $server = Models\Server::findOrFail($id);
978
        $node = Models\Node::findOrFail($server->node);
979
980
        $validator = Validator::make([
981
            'password' => $password,
982
        ], [
983
            'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/',
984
        ]);
985
986
        if ($validator->fails()) {
987
            throw new DisplayValidationException(json_encode($validator->errors()));
988
        }
989
990
        DB::beginTransaction();
991
        $server->sftp_password = Crypt::encrypt($password);
992
993
        try {
994
            $server->save();
995
996
            $client = Models\Node::guzzleRequest($server->node);
997
            $client->request('POST', '/server/password', [
998
                'headers' => [
999
                    'X-Access-Token' => $node->daemonSecret,
1000
                    'X-Access-Server' => $server->uuid,
1001
                ],
1002
                'json' => [
1003
                    'password' => $password,
1004
                ],
1005
            ]);
1006
1007
            DB::commit();
1008
1009
            return true;
1010
        } catch (\GuzzleHttp\Exception\TransferException $ex) {
1011
            DB::rollBack();
1012
            throw new DisplayException('There was an error while attmping to contact the remote service to change the password.', $ex);
1013
        } catch (\Exception $ex) {
1014
            DB::rollBack();
1015
            throw $ex;
1016
        }
1017
    }
1018
}
1019