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 ;
1926use ILIAS \BackgroundTasks \Implementation \Tasks \UserInteraction \UserInteractionOption ;
2027use ILIAS \components \OrgUnit \ARHelper \DIC ;
2128
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