Skip to content

Commit e5af5a7

Browse files
chfsxfwolf-ilias
authored andcommitted
0045883, 0045884, 0045900 Several BGT Issues
Signed-off-by: Releasemanager <webmaster@ilias.de>
1 parent d48c647 commit e5af5a7

1 file changed

Lines changed: 190 additions & 36 deletions

File tree

components/ILIAS/BackgroundTasks_/classes/class.ilBTControllerGUI.php

Lines changed: 190 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616
*
1717
*********************************************************************/
1818

19+
use ILIAS\HTTP\StatusCode;
20+
use ILIAS\Filesystem\Stream\Streams;
21+
use ILIAS\Data\URI;
22+
use ILIAS\Data\Factory;
23+
use ILIAS\BackgroundTasks\Bucket;
24+
use ILIAS\Filesystem\Stream\Stream;
25+
use ILIAS\HTTP\Response\ResponseHeader;
1926
use ILIAS\BackgroundTasks\Implementation\Tasks\UserInteraction\UserInteractionOption;
2027
use ILIAS\components\OrgUnit\ARHelper\DIC;
2128

@@ -25,7 +32,7 @@
2532
* @author Oskar Truffer <ot@studer-raimann.ch>
2633
* @author Fabian Schmid <fs@studer-raimann.ch>
2734
*/
28-
class ilBTControllerGUI implements ilCtrlBaseClassInterface
35+
class ilBTControllerGUI implements ilCtrlBaseClassInterface, ilCtrlSecurityInterface
2936
{
3037
use DIC;
3138
public const FROM_URL = 'from_url';
@@ -37,7 +44,6 @@ class ilBTControllerGUI implements ilCtrlBaseClassInterface
3744
public const IS_ASYNC = 'bt_task_is_async';
3845
public const CMD_GET_REPLACEMENT_ITEM = "getAsyncReplacementItem";
3946

40-
4147
public function executeCommand(): void
4248
{
4349
$cmd = $this->ctrl()->getCmd();
@@ -57,38 +63,55 @@ public function executeCommand(): void
5763
}
5864
}
5965

66+
public function getUnsafeGetCommands(): array
67+
{
68+
return array_unique([
69+
self::CMD_ABORT,
70+
self::CMD_REMOVE,
71+
self::CMD_USER_INTERACTION,
72+
]);
73+
}
6074

61-
protected function userInteraction(): void
75+
public function getSafePostCommands(): array
6276
{
63-
$observer_id = (int) $this->http()->request()->getQueryParams()[self::OBSERVER_ID];
64-
$selected_option = $this->http()->request()->getQueryParams()[self::SELECTED_OPTION];
65-
$from_url = $this->getFromURL();
77+
return [];
78+
}
6679

67-
$observer = $this->dic()->backgroundTasks()->persistence()->loadBucket($observer_id);
68-
if ($observer->getUserId() !== $this->user()->getId()) {
69-
return;
70-
}
71-
$option = new UserInteractionOption("", $selected_option);
72-
$this->dic()->backgroundTasks()->taskManager()->continueTask($observer, $option);
73-
if ($this->http()->request()->getQueryParams()[self::IS_ASYNC] === "true") {
74-
$this->http()->close();
80+
protected function userInteraction(): void
81+
{
82+
$observer_id = $this->retrieveObserverIdFromRequest();
83+
$selected_option = $this->retrieveSelectedInteractionOption();
84+
if ($observer_id === null || $selected_option === null) {
85+
$this->respondWithError(StatusCode::HTTP_BAD_REQUEST, 'Bad Request');
7586
}
76-
$this->ctrl()->redirectToURL($from_url);
87+
88+
$bucket = $this->dic()->backgroundTasks()->persistence()->loadBucket($observer_id);
89+
90+
$this->enforceBucketBelongsToCurrentUser($bucket);
91+
92+
$this->dic()->backgroundTasks()->taskManager()->continueTask(
93+
$bucket,
94+
new UserInteractionOption('', $selected_option)
95+
);
96+
97+
$this->redirectToCallerOrClose();
7798
}
7899

79100

80101
protected function abortBucket(): void
81102
{
82-
$observer_id = (int) $this->http()->request()->getQueryParams()[self::OBSERVER_ID];
83-
$from_url = $this->getFromURL();
103+
$observer_id = $this->retrieveObserverIdFromRequest();
104+
if ($observer_id === null) {
105+
$this->respondWithError(StatusCode::HTTP_BAD_REQUEST, 'Bad Request');
106+
}
84107

85108
$bucket = $this->dic()->backgroundTasks()->persistence()->loadBucket($observer_id);
86109

110+
$this->enforceBucketBelongsToCurrentUser($bucket);
111+
87112
$this->dic()->backgroundTasks()->taskManager()->quitBucket($bucket);
88-
if ($this->http()->request()->getQueryParams()[self::IS_ASYNC] === "true") {
89-
exit;
90-
}
91-
$this->ctrl()->redirectToURL($from_url);
113+
114+
$this->redirectToCallerOrClose();
92115
}
93116

94117

@@ -98,37 +121,168 @@ protected function abortBucket(): void
98121
*/
99122
protected function getAsyncReplacementItem(): void
100123
{
101-
$observer_id = (int) $this->http()->request()->getQueryParams()[self::OBSERVER_ID];
124+
$observer_id = $this->retrieveObserverIdFromRequest();
125+
if ($observer_id === null) {
126+
$this->respondWithError(StatusCode::HTTP_BAD_REQUEST, 'Bad Request');
127+
}
128+
102129
$bucket = $this->dic()->backgroundTasks()->persistence()->loadBucket($observer_id);
103130

131+
$this->enforceBucketBelongsToCurrentUser($bucket);
132+
104133
$item_source = new ilBTPopOverGUI($this->dic());
105134
$this->dic()->language()->loadLanguageModule('background_tasks');
106135
$item = $item_source->getItemForObserver($bucket);
107-
echo $this->dic()->ui()->renderer()->renderAsync($item);
108-
exit;
136+
137+
$this->sendSuccessResponse(
138+
Streams::ofString(
139+
$this->dic()->ui()->renderer()->renderAsync($item)
140+
)
141+
);
109142
}
110143

111144

112-
protected function getFromURL(): string
145+
private function getFromURL(): URI
113146
{
114-
return self::unhash($this->http()->request()->getQueryParams()[self::FROM_URL]);
115-
}
147+
$uri = (new Factory())->uri($this->defaultReturnUrl());
116148

149+
$decoded_from_url = $this->retrieveFromUrlFromRequest();
150+
if ($decoded_from_url === null || $decoded_from_url === '') {
151+
return $uri;
152+
}
117153

118-
/**
119-
* @param $url
120-
*/
121-
public static function hash($url): string
154+
$from_url = self::unhash($decoded_from_url);
155+
if ($from_url === false) {
156+
return $uri;
157+
}
158+
159+
$from_url_parts = parse_url($from_url);
160+
if (!is_array($from_url_parts)) {
161+
return $uri;
162+
}
163+
164+
$uri = $uri->withPath(null)->withQuery(null)->withFragment(null);
165+
166+
$mutators = [
167+
'path' => static fn(URI $u, string $v): URI => $u->withPath($v),
168+
'query' => static fn(URI $u, string $v): URI => $u->withQuery($v),
169+
'fragment' => static fn(URI $u, string $v): URI => $u->withFragment($v),
170+
];
171+
172+
foreach ($mutators as $key => $apply) {
173+
$value = $from_url_parts[$key] ?? null;
174+
if (is_string($value) && $value !== '') {
175+
$uri = $apply($uri, $value);
176+
}
177+
}
178+
179+
return $uri;
180+
}
181+
182+
public static function hash(string $url): string
122183
{
123184
return base64_encode($url);
124185
}
125186

126-
127-
/**
128-
* @param $url
129-
*/
130-
public static function unhash($url): string
187+
public static function unhash(string $url): string|false
131188
{
132189
return base64_decode($url);
133190
}
191+
192+
private function enforceBucketBelongsToCurrentUser(Bucket $bucket): void
193+
{
194+
if ($bucket->getUserId() !== $this->user()->getId()) {
195+
$this->respondWithError(StatusCode::HTTP_FORBIDDEN, 'Forbidden');
196+
}
197+
}
198+
199+
private function defaultReturnUrl(): string
200+
{
201+
return ilUtil::_getHttpPath();
202+
}
203+
204+
private function sendSuccessResponse(Stream $stream): void
205+
{
206+
$response = $this->http()->response()
207+
->withStatus(StatusCode::HTTP_OK, 'OK')
208+
->withBody($stream);
209+
$this->http()->saveResponse($response);
210+
211+
if ($this->wasInvokedAsynchronously()) {
212+
$this->http()->sendResponse();
213+
$this->http()->close();
214+
}
215+
}
216+
217+
private function redirectToCallerOrClose(): never
218+
{
219+
if (!$this->wasInvokedAsynchronously()) {
220+
$this->ctrl()->redirectToURL((string) $this->getFromURL());
221+
}
222+
223+
$this->http()->close();
224+
}
225+
226+
private function respondWithError(int $error_status_code, string $message): never
227+
{
228+
$response = $this->http()->response()->withStatus($error_status_code, $message);
229+
if ($error_status_code === StatusCode::HTTP_FORBIDDEN && !$this->wasInvokedAsynchronously()) {
230+
$this->tpl()->setOnScreenMessage(
231+
$this->tpl()::MESSAGE_TYPE_FAILURE,
232+
$this->lng()->txt('permission_denied'),
233+
true
234+
);
235+
$response = $response
236+
->withStatus(StatusCode::HTTP_FOUND, 'Found')
237+
->withHeader(ResponseHeader::LOCATION, $this->defaultReturnUrl());
238+
}
239+
240+
$this->http()->saveResponse($response);
241+
$this->http()->sendResponse();
242+
$this->http()->close();
243+
}
244+
245+
private function retrieveSelectedInteractionOption(): ?string
246+
{
247+
return $this->http()->wrapper()->query()->retrieve(
248+
self::SELECTED_OPTION,
249+
$this->dic()->refinery()->byTrying([
250+
$this->dic()->refinery()->kindlyTo()->string(),
251+
$this->dic()->refinery()->always(null)
252+
])
253+
);
254+
}
255+
256+
private function wasInvokedAsynchronously(): bool
257+
{
258+
return $this->http()->wrapper()->query()->retrieve(
259+
self::IS_ASYNC,
260+
$this->dic()->refinery()->byTrying([
261+
$this->dic()->refinery()->kindlyTo()->bool(),
262+
$this->dic()->refinery()->always(false)
263+
])
264+
);
265+
}
266+
267+
private function retrieveObserverIdFromRequest(): ?int
268+
{
269+
return $this->http()->wrapper()->query()->retrieve(
270+
self::OBSERVER_ID,
271+
$this->dic()->refinery()->byTrying([
272+
$this->dic()->refinery()->kindlyTo()->int(),
273+
$this->dic()->refinery()->always(null)
274+
])
275+
);
276+
}
277+
278+
private function retrieveFromUrlFromRequest(): ?string
279+
{
280+
return $this->http()->wrapper()->query()->retrieve(
281+
self::FROM_URL,
282+
$this->dic()->refinery()->byTrying([
283+
$this->dic()->refinery()->kindlyTo()->string(),
284+
$this->dic()->refinery()->always(null)
285+
])
286+
);
287+
}
134288
}

0 commit comments

Comments
 (0)