ILIAS  trunk Revision v12.0_alpha-377-g3641b37b9db
XapiProxyRequest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21namespace XapiProxy;
22
23use GuzzleHttp\Psr7\Request;
24use GuzzleHttp\Psr7\Uri;
26
28{
29 private Container $dic;
30
32
34
35 private string $cmdPart2plus = "";
36 private bool $checkGetStatements = true;
37
39 {
40 $this->dic = $GLOBALS['DIC'];
41 $this->xapiproxy = $xapiproxy;
42 $this->cmdPart2plus = "";
43 }
44
45 public function handle(): void
46 {
47 $this->xapiProxyResponse = $this->xapiproxy->getXapiProxyResponse();
48 $request = $this->dic->http()->request();
49 $cmdParts = $this->xapiproxy->cmdParts();
50 $this->xapiproxy->log()->debug($this->msg(var_export($cmdParts, true)));
51 if (count($cmdParts) === 5) {
52 $cmd = $cmdParts[3];
53 if ($cmd === "statements") {
54 $this->handleStatementsRequest($request);
55 } elseif ($cmd === "activities") {
56 $this->handleActivitiesRequest($request);
57 } elseif ($cmd === "activities/profile") {
58 $this->handleActivitiesProfileRequest($request);
59 } elseif ($cmd === "activities/state") {
60 $this->handleActivitiesStateRequest($request);
61 } elseif ($cmd === "agents") {
62 $this->handleAgentsRequest($request);
63 } elseif ($cmd === "agents/profile") {
64 $this->handleAgentsProfileRequest($request);
65 } elseif ($cmd === "about") {
66 $this->handleAboutRequest($request);
67 } else {
68 $this->xapiproxy->log()->debug($this->msg("Wrong xApi Query: " . $request->getUri()));
69 $this->xapiProxyResponse->exitBadRequest();
70 }
71 } else {
72 $this->xapiproxy->log()->error($this->msg("Wrong xApi Query: " . $request->getUri()));
73 $this->xapiProxyResponse->exitBadRequest();
74 }
75 }
76
77 private function msg(string $msg): string
78 {
79 return $this->xapiproxy->msg($msg);
80 }
81
82 private function handleStatementsRequest(\Psr\Http\Message\RequestInterface $request): void
83 {
84 $this->xapiproxy->log()->debug($this->msg("handleStatementsRequest (" . $this->xapiproxy->method() . "): " . $request->getUri()));
85 $method = $this->xapiproxy->method();
86 if ($method === "post" || $method === "put") {
87 $this->handlePostPutStatementsRequest($request);
88 } elseif ($method === "get") {
89 $this->handleGetStatementsRequest($request);
90 } else {
91 $this->xapiProxyResponse->exitBadRequest();
92 }
93 }
94
95 private function handleGetStatementsRequest(\Psr\Http\Message\RequestInterface $request): void
96 {
97 if ($this->xapiproxy->cmdParts()[4] == "") {
98 $this->xapiproxy->log()->warning($this->msg("unfiltered get statements requests are not allowed for security reasons"));
99 $this->xapiProxyResponse->exitBadRequest();
100 }
101 $this->xapiproxy->log()->debug($this->msg("handleGetStatementsRequest: " . $request->getUri()));
102
103 try {
104 $badRequest = false;
105 if ($this->checkGetStatements) {
106 $authToken = \ilCmiXapiAuthToken::getInstanceByToken($this->xapiproxy->token());
107 $obj = \ilObjCmiXapi::getInstance($authToken->getRefId(), true);
108 $access = \ilCmiXapiAccess::getInstance($obj);
109 $params = $this->dic->http()->wrapper()->query();
110 if ($params->has('statementId')) {
111 $this->xapiproxy->log()->debug($this->msg("single statementId requests can not be secured. It is not allowed to append any additional parameter like registration or activity (tested in LL7)"));
112 // single statementId can not be handled. it is not allowed to append a registration on single statement requests (tested in LL7)
113 $this->handleProxy($request);
114 } else {
115 if ($params->has('activity')) {
116 // ToDo: how this can be verified? the object only knows the top activityId
117 $this->handleProxy($request);
118 } else {
119 $this->xapiproxy->log()->debug($this->msg("add activity: " . $obj->getActivityId()));
120 $this->cmdPart2plus .= "&activity=" . $obj->getActivityId() . "&related_activities=true";
121 }
122 if (!$access->hasOutcomesAccess($authToken->getUsrId())) {
123 // ToCheck
124 /*
125 if (!$access->hasStatementsAccess()) {
126 $this->xapiproxy->log()->warning($this->msg("statements access is not enabled"));
127 $this->xapiProxyResponse->exitBadRequest();
128 }
129 */
130 if ($obj->getContentType() == \ilObjCmiXapi::CONT_TYPE_CMI5) {
131 $regUserObject = \ilCmiXapiUser::getCMI5RegistrationFromAuthToken($authToken);
132 } else {
133 $regUserObject = \ilCmiXapiUser::getRegistrationFromAuthToken($authToken);
134 }
135 if ($params->has('registration')) {
136 $regParam = $params->retrieve('registration', $this->dic->refinery()->kindlyTo()->string());
137 if ($regParam != $regUserObject) {
138 $this->xapiproxy->log()->debug($this->msg("wrong registration: " . $regParam . " != " . $regUserObject));
139 $badRequest = true;
140 }
141 } else { // add registration
142 $this->xapiproxy->log()->debug($this->msg("add registration: " . $regUserObject));
143 $this->cmdPart2plus .= "&registration=" . $regUserObject;
144 }
145 }
146 }
147 }
148 if ($badRequest) {
149 $this->xapiProxyResponse->exitBadRequest();
150 } else {
151 $this->handleProxy($request);
152 }
153 } catch (\Exception $e) {
154 $this->xapiproxy->log()->error($this->msg($e->getMessage()));
155 }
156 }
157
158 private function handlePostPutStatementsRequest(\Psr\Http\Message\RequestInterface $request): void
159 {
160 $this->xapiproxy->log()->debug($this->msg("handlePostPutStatementsRequest: " . $request->getUri()));
161 $body = $request->getBody()->getContents();
162 $fakePostBody = null;
163 if (empty($body)) {
164 $this->xapiproxy->log()->warning($this->msg("empty body in handlePostPutRequest"));
165 $this->handleProxy($request);
166 } else {
167 try {
168 $this->xapiproxy->log()->debug($this->msg("process statements"));
169 $retArr = $this->xapiproxy->processStatements($request, $body);
170 if (is_array($retArr)) {
171 $body = json_encode($retArr[0]); // new body with allowed statements
172 $fakePostBody = $retArr[1]; // fake post php array of ALL statments as if all statements were processed
173 }
174 } catch (\Exception $e) {
175 $this->xapiproxy->log()->error($this->msg($e->getMessage()));
176 $this->xapiProxyResponse->exitProxyError();
177 }
178 try {
179 $body = $this->xapiproxy->modifyBody($body);
180 $req = new Request($request->getMethod(), $request->getUri(), $request->getHeaders(), $body);
181 $this->handleProxy($req, $fakePostBody);
182 } catch (\Exception $e) {
183 $this->xapiproxy->log()->error($this->msg($e->getMessage()));
184 $this->handleProxy($request, $fakePostBody);
185 }
186 }
187 }
188
189 private function handleActivitiesRequest(\Psr\Http\Message\RequestInterface $request): void
190 {
191 // $this->xapiproxy->log()->debug($this->msg("blocked handleActivitiesRequest (" . $this->xapiproxy->method() . "): " . $request->getUri()));
192 // $this->xapiProxyResponse->exitBadRequest();
193 $this->xapiproxy->log()->debug($this->msg("handleActivitiesRequest (" . $this->xapiproxy->method() . "): " . $request->getUri()));
194 $this->handleProxy($request);
195 }
196
197 private function handleActivitiesProfileRequest(\Psr\Http\Message\RequestInterface $request): void
198 {
199 $this->xapiproxy->log()->debug($this->msg("handleActivitiesProfileRequest (" . $this->xapiproxy->method() . "): " . $request->getUri()));
200 $this->handleProxy($request);
201 }
202
203 private function handleActivitiesStateRequest(\Psr\Http\Message\RequestInterface $request): void
204 {
205 $this->xapiproxy->log()->debug($this->msg("handleActivitiesStateRequest (" . $this->xapiproxy->method() . "): " . $request->getUri()));
206 $this->handleProxy($request);
207 }
208
209 private function handleAgentsRequest(\Psr\Http\Message\RequestInterface $request): void
210 {
211 $this->xapiproxy->log()->debug($this->msg("blocked handleAgentsRequest (" . $this->xapiproxy->method() . "): " . $request->getUri()));
212 $this->xapiProxyResponse->exitBadRequest();
213 }
214
215 private function handleAgentsProfileRequest(\Psr\Http\Message\RequestInterface $request): void
216 {
217 $this->xapiproxy->log()->debug($this->msg("handleAgentsProfileRequest (" . $this->xapiproxy->method() . "): " . $request->getUri()));
218 $this->handleProxy($request);
219 }
220
221 private function handleAboutRequest(\Psr\Http\Message\RequestInterface $request): void
222 {
223 $this->xapiproxy->log()->debug($this->msg("handleAboutRequest (" . $this->xapiproxy->method() . "): " . $request->getUri()));
224 $this->handleProxy($request);
225 }
226
227
228 private function handleProxy(\Psr\Http\Message\RequestInterface $request, $fakePostBody = null): void
229 {
230 $endpointDefault = $this->xapiproxy->getDefaultLrsEndpoint();
231 $endpointFallback = $this->xapiproxy->getFallbackLrsEndpoint();
232
233 $this->xapiproxy->log()->debug($this->msg("endpointDefault: " . $endpointDefault));
234 $this->xapiproxy->log()->debug($this->msg("endpointFallback: " . $endpointFallback));
235
236 $keyDefault = $this->xapiproxy->getDefaultLrsKey();
237 $secretDefault = $this->xapiproxy->getDefaultLrsSecret();
238 $authDefault = 'Basic ' . base64_encode($keyDefault . ':' . $secretDefault);
239
240 $hasFallback = ($endpointFallback !== "");
241
242 if ($hasFallback) {
243 $keyFallback = $this->xapiproxy->getFallbackLrsKey();
244 $secretFallback = $this->xapiproxy->getFallbackLrsSecret();
245 $authFallback = 'Basic ' . base64_encode($keyFallback . ':' . $secretFallback);
246 }
247
248 $cmd = $this->xapiproxy->cmdParts()[2] . $this->cmdPart2plus;
249 $upstreamDefault = $endpointDefault . $cmd;
250 $body = $request->getBody()->getContents();
251
252 // Default request ausführen
253 $responseDefault = $this->sendCurlRequest($upstreamDefault, $authDefault, $request->getMethod(), $body);
254
255 // Falls Fallback definiert, zweites Ziel testen
256 $responseFallback = null;
257 if ($hasFallback) {
258 $upstreamFallback = $endpointFallback . $cmd;
259 $responseFallback = $this->sendCurlRequest($upstreamFallback, $authFallback, $request->getMethod(), $body);
260 }
261
262 // Reaktionen auswerten
263 $defaultOk = $this->xapiProxyResponse->checkResponse($responseDefault, $endpointDefault);
264 $fallbackOk = $hasFallback ? $this->xapiProxyResponse->checkResponse($responseFallback, $endpointFallback) : false;
265
266 if ($defaultOk) {
267 try {
268 $this->xapiProxyResponse->handleResponse($request, $responseDefault, $fakePostBody);
269 } catch (\Exception $e) {
270 $this->xapiProxyResponse->exitProxyError();
271 }
272 } elseif ($fallbackOk) {
273 try {
274 $this->xapiProxyResponse->handleResponse($request, $responseFallback, $fakePostBody);
275 } catch (\Exception $e) {
276 $this->xapiProxyResponse->exitProxyError();
277 }
278 } else {
279 $this->xapiProxyResponse->exitResponseError();
280 }
281 }
282
283 private function sendCurlRequest(string $url, string $authHeader, string $method, string $body = ''): \GuzzleHttp\Psr7\Response
284 {
285 $ch = curl_init($url);
286 $headers = [
287 "Authorization: $authHeader",
288 "X-Experience-API-Version: 1.0.3",
289 "Accept: application/json",
290 "Content-Type: application/json"
291 ];
292
293 curl_setopt_array($ch, [
294 CURLOPT_RETURNTRANSFER => true,
295 CURLOPT_FOLLOWLOCATION => true,
296 CURLOPT_CONNECTTIMEOUT => 10,
297 CURLOPT_TIMEOUT => 30,
298 CURLOPT_SSL_VERIFYPEER => true,
299 CURLOPT_HTTPHEADER => $headers,
300 CURLOPT_CUSTOMREQUEST => strtoupper($method),
301 ]);
302
303 if (in_array(strtoupper($method), ['POST', 'PUT', 'PATCH'])) {
304 curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
305 }
306
307 $rawBody = curl_exec($ch);
308 $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
309 $curlError = curl_error($ch);
310 curl_close($ch);
311
312 if ($curlError) {
313 $this->xapiproxy->log()->error("cURL error for $url: $curlError");
314 $rawBody = '';
315 }
316
317 return new \GuzzleHttp\Psr7\Response($statusCode, [], $rawBody);
318 }
319
320 private function createProxyRequest(\Psr\Http\Message\RequestInterface $request, \GuzzleHttp\Psr7\Uri $uri, string $auth, string $body): \GuzzleHttp\Psr7\Request
321 {
322 $headers = array(
323 'Cache-Control' => 'no-cache, no-store, must-revalidate',
324 'Authorization' => $auth
325 );
326
327 if ($request->hasHeader('X-Experience-API-Version')) {
328 $headers['X-Experience-API-Version'] = $request->getHeader('X-Experience-API-Version');
329 }
330
331 if ($request->hasHeader('Referrer')) {
332 $headers['Referrer'] = $request->getHeader('Referrer');
333 }
334
335 if ($request->hasHeader('Content-Type')) {
336 $headers['Content-Type'] = $request->getHeader('Content-Type');
337 }
338
339 if ($request->hasHeader('Origin')) {
340 $headers['Origin'] = $request->getHeader('Origin');
341 }
342
343 if ($request->hasHeader('Content-Length')) {
344 $contentLength = $request->getHeader('Content-Length');
345 if (is_array($contentLength) && $contentLength[0] === '') {
346 $contentLength = array(0);
347 } elseif ($contentLength === '') {
348 $contentLength = array(0);
349 }
350 $headers['Content-Length'] = $contentLength;
351 }
352
353 if ($request->hasHeader('Connection')) {
354 $headers['Connection'] = $request->getHeader('Connection');
355 }
356
357 //$this->xapiproxy->log()->debug($this->msg($body));
358
359 $req = new Request(strtoupper($request->getMethod()), $uri, $headers, $body);
360
361 return $req;
362 }
363}
Customizing of pimple-DIC for ILIAS.
Definition: Container.php:36
__construct(XapiProxy $xapiproxy)
XapiProxyResponse $xapiProxyResponse
handleActivitiesStateRequest(\Psr\Http\Message\RequestInterface $request)
handlePostPutStatementsRequest(\Psr\Http\Message\RequestInterface $request)
handleAboutRequest(\Psr\Http\Message\RequestInterface $request)
handleStatementsRequest(\Psr\Http\Message\RequestInterface $request)
handleActivitiesProfileRequest(\Psr\Http\Message\RequestInterface $request)
sendCurlRequest(string $url, string $authHeader, string $method, string $body='')
handleActivitiesRequest(\Psr\Http\Message\RequestInterface $request)
handleGetStatementsRequest(\Psr\Http\Message\RequestInterface $request)
handleAgentsRequest(\Psr\Http\Message\RequestInterface $request)
handleAgentsProfileRequest(\Psr\Http\Message\RequestInterface $request)
handleProxy(\Psr\Http\Message\RequestInterface $request, $fakePostBody=null)
createProxyRequest(\Psr\Http\Message\RequestInterface $request, \GuzzleHttp\Psr7\Uri $uri, string $auth, string $body)
static getInstance(ilObjCmiXapi $object)
static getInstanceByToken(string $token)
static getCMI5RegistrationFromAuthToken(ilCmiXapiAuthToken $authToken)
static getRegistrationFromAuthToken(ilCmiXapiAuthToken $authToken)
static getInstance(int $a_id=0, bool $a_reference=true)
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
Definition: ltiregstart.php:31
catch(\Exception $e) $req
Definition: xapiproxy.php:78
$url
Definition: shib_logout.php:68
$GLOBALS["DIC"]
Definition: wac.php:54