forked from pterodactyl/panel
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSftpAuthenticationController.php
More file actions
168 lines (142 loc) · 6.06 KB
/
SftpAuthenticationController.php
File metadata and controls
168 lines (142 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
<?php
namespace Pterodactyl\Http\Controllers\Api\Remote;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Facades\Activity;
use Pterodactyl\Models\Permission;
use phpseclib3\Crypt\PublicKeyLoader;
use Pterodactyl\Http\Controllers\Controller;
use phpseclib3\Exception\NoKeyLoadedException;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Pterodactyl\Exceptions\Http\HttpForbiddenException;
use Pterodactyl\Services\Servers\GetUserPermissionsService;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
class SftpAuthenticationController extends Controller
{
use ThrottlesLogins;
protected GetUserPermissionsService $permissions;
public function __construct(GetUserPermissionsService $permissions)
{
$this->permissions = $permissions;
}
/**
* Authenticate a set of credentials and return the associated server details
* for a SFTP connection on the daemon. This supports both public key and password
* based credentials.
*/
public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse
{
$connection = $this->parseUsername($request->input('username'));
if (empty($connection['server'])) {
throw new BadRequestHttpException('No valid server identifier was included in the request.');
}
if ($this->hasTooManyLoginAttempts($request)) {
$seconds = $this->limiter()->availableIn($this->throttleKey($request));
throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in {$seconds} seconds.");
}
$user = $this->getUser($request, $connection['username']);
$server = $this->getServer($request, $connection['server']);
if ($request->input('type') !== 'public_key') {
if (!password_verify($request->input('password'), $user->password)) {
Activity::event('auth:sftp.fail')->property('method', 'password')->subject($user)->log();
$this->reject($request);
}
} else {
$key = null;
try {
$key = PublicKeyLoader::loadPublicKey(trim($request->input('password')));
} catch (NoKeyLoadedException $exception) {
// do nothing
}
if (!$key || !$user->sshKeys()->where('fingerprint', $key->getFingerprint('sha256'))->exists()) {
// We don't log here because of the way the SFTP system works. This endpoint
// will get hit for every key the user provides, which could be 4 or 5. That is
// a lot of unnecessary log noise.
//
// For now, we'll only log failures due to a bad password as those are not likely
// to occur more than once in a session for the user, and are more likely to be of
// value to the end user.
$this->reject($request, is_null($key));
}
}
$this->validateSftpAccess($user, $server);
return new JsonResponse([
'user' => $user->uuid,
'server' => $server->uuid,
'permissions' => $this->permissions->handle($server, $user),
]);
}
/**
* Finds the server being requested and ensures that it belongs to the node this
* request stems from.
*/
protected function getServer(Request $request, string $uuid): Server
{
return Server::query()
->where(fn ($builder) => $builder->where('uuid', $uuid)->orWhere('uuidShort', $uuid))
->where('node_id', $request->attributes->get('node')->id)
->firstOr(function () use ($request) {
$this->reject($request);
});
}
/**
* Finds a user with the given username or increments the login attempts.
*/
protected function getUser(Request $request, string $username): User
{
return User::query()->where('username', $username)->firstOr(function () use ($request) {
$this->reject($request);
});
}
/**
* Parses the username provided to the request.
*
* @return array{"username": string, "server": string}
*/
protected function parseUsername(string $value): array
{
// Reverse the string to avoid issues with usernames that contain periods.
$parts = explode('.', strrev($value), 2);
// Unreverse the strings after parsing them apart.
return [
'username' => strrev(array_get($parts, 1)),
'server' => strrev(array_get($parts, 0)),
];
}
/**
* Rejects the request and increments the login attempts.
*/
protected function reject(Request $request, bool $increment = true): void
{
if ($increment) {
$this->incrementLoginAttempts($request);
}
throw new HttpForbiddenException('Authorization credentials were not correct, please try again.');
}
/**
* Validates that a user should have permission to use SFTP for the given server.
*/
protected function validateSftpAccess(User $user, Server $server): void
{
if (!$user->root_admin && $server->owner_id !== $user->id) {
$permissions = $this->permissions->handle($server, $user);
if (!in_array(Permission::ACTION_FILE_SFTP, $permissions)) {
Activity::event('server:sftp.denied')->actor($user)->subject($server)->log();
throw new HttpForbiddenException('You do not have permission to access SFTP for this server.');
}
}
$server->validateCurrentState();
}
/**
* Get the throttle key for the given request.
*/
protected function throttleKey(Request $request): string
{
$username = explode('.', strrev($request->input('username', '')));
return strtolower(strrev($username[0] ?? '') . '|' . $request->ip());
}
}