-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathHarpController.php
More file actions
166 lines (144 loc) · 5.08 KB
/
Copy pathHarpController.php
File metadata and controls
166 lines (144 loc) · 5.08 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
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\AppAPI\Controller;
use OCA\AppAPI\AppInfo\Application;
use OCA\AppAPI\Db\ExApp;
use OCA\AppAPI\Service\ExAppService;
use OCA\AppAPI\Service\HarpService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\ICrypto;
use Psr\Log\LoggerInterface;
class HarpController extends Controller {
protected $request;
public function __construct(
IRequest $request,
private readonly ExAppService $exAppService,
private readonly LoggerInterface $logger,
private readonly IThrottler $throttler,
private readonly IUserManager $userManager,
private readonly IGroupManager $groupManager,
private readonly ICrypto $crypto,
private readonly ?string $userId,
private readonly HarpService $harpService,
) {
parent::__construct(Application::APP_ID, $request);
$this->request = $request;
}
private function validateHarpSharedKey(ExApp $exApp): bool {
try {
if (!isset($exApp->getDeployConfig()['haproxy_password'])) {
$this->logger->error('Harp shared key is not set. Invalid daemon config.');
return false;
}
$harpKey = $this->crypto->decrypt($exApp->getDeployConfig()['haproxy_password']);
} catch (\Exception $e) {
$this->logger->error('Failed to decrypt harp shared key. Invalid daemon config.', ['exception' => $e]);
return false;
}
$headerHarpKey = $this->request->getHeader('HARP-SHARED-KEY');
if ($headerHarpKey === '' || $headerHarpKey !== $harpKey) {
$this->logger->error('Harp shared key is not valid');
$this->throttler->registerAttempt(Application::APP_ID, $this->request->getRemoteAddress(), [
'appid' => $exApp->getAppid(),
]);
return false;
}
return true;
}
#[PublicPage]
#[NoCSRFRequired]
public function getExAppMetadata(string $appId): DataResponse {
$exApp = $this->exAppService->getExApp($appId);
if ($exApp === null) {
$this->logger->error('ExApp not found', ['appId' => $appId]);
// return the same response as invalid harp key to prevent ex-app guessing
return new DataResponse(['message' => 'Harp shared key is not valid'], Http::STATUS_UNAUTHORIZED);
}
if (!$this->validateHarpSharedKey($exApp)) {
// Protection for guessing HaRP shared key
$this->throttler->registerAttempt(Application::APP_ID, $this->request->getRemoteAddress(), [
'appid' => $appId,
]);
$this->logger->error('Harp shared key is not valid', ['appId' => $appId]);
return new DataResponse(['message' => 'Harp shared key is not valid'], Http::STATUS_UNAUTHORIZED);
}
return new DataResponse($this->harpService->getHarpExApp($exApp));
}
protected function isUserEnabled(string $userId): bool {
$user = $this->userManager->get($userId);
if ($user === null) {
$this->logger->debug('User not found', ['userId' => $userId]);
return false;
}
if (!$user->isEnabled()) {
$this->logger->debug('User is not enabled', ['userId' => $userId]);
return false;
}
return true;
}
/**
* access_level:
* 0: PUBLIC
* 1: USER
* 2: ADMIN
* @return DataResponse array{ user_id: string, access_level: int }
*/
#[PublicPage]
#[NoCSRFRequired]
public function getUserInfo(string $appId): DataResponse {
$exApp = $this->exAppService->getExApp($appId);
if ($exApp === null) {
$this->logger->error('ExApp not found', ['appId' => $appId]);
// Protection for guessing installed ExApps list
$this->throttler->registerAttempt(Application::APP_ID, $this->request->getRemoteAddress(), [
'appid' => $appId,
]);
// return the same response as invalid harp key to prevent ex-app guessing
return new DataResponse(['message' => 'Harp shared key is not valid'], Http::STATUS_UNAUTHORIZED);
}
if (!$this->validateHarpSharedKey($exApp)) {
return new DataResponse(['message' => 'Harp shared key is not valid'], Http::STATUS_UNAUTHORIZED);
}
if ($this->userId === null) {
$this->logger->debug('No user found in the harp request');
return new DataResponse([
'user_id' => '',
'access_level' => ExAppRouteAccessLevel::PUBLIC->value,
]);
}
if (!$this->isUserEnabled($this->userId)) {
$this->logger->debug('User is not enabled in the harp request', ['userId' => $this->userId]);
return new DataResponse([
'user_id' => $this->userId,
'access_level' => ExAppRouteAccessLevel::PUBLIC->value,
]);
}
if ($this->groupManager->isAdmin($this->userId)) {
return new DataResponse([
'user_id' => $this->userId,
'access_level' => ExAppRouteAccessLevel::ADMIN->value,
]);
}
return new DataResponse([
'user_id' => $this->userId,
'access_level' => ExAppRouteAccessLevel::USER->value,
]);
}
}
enum ExAppRouteAccessLevel: int {
case PUBLIC = 0;
case USER = 1;
case ADMIN = 2;
}