Passed
Pull Request — master (#5629)
by Angel Fernando Quiroz
10:06 queued 01:32
created

LrsRequest::alternateRequestSyntax()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 48
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 31
nc 9
nop 0
dl 0
loc 48
rs 8.4906
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\PluginBundle\XApi\Lrs;
8
9
use Chamilo\CoreBundle\Entity\XApiLrsAuth;
10
use Database;
11
use Exception;
12
use Symfony\Component\HttpFoundation\Request as HttpRequest;
13
use Symfony\Component\HttpFoundation\Response as HttpResponse;
14
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
15
use Symfony\Component\HttpKernel\Exception\HttpException;
16
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
17
use Xabbuh\XApi\Common\Exception\AccessDeniedException;
18
use Xabbuh\XApi\Common\Exception\XApiException;
19
20
/**
21
 * Class LrsRequest.
22
 */
23
class LrsRequest
24
{
25
    /**
26
     * @var HttpRequest
27
     */
28
    private $request;
29
30
    public function __construct()
31
    {
32
        $this->request = HttpRequest::createFromGlobals();
33
    }
34
35
    public function send(): void
36
    {
37
        try {
38
            $this->alternateRequestSyntax();
39
40
            $controllerName = $this->getControllerName();
41
            $methodName = $this->getMethodName();
42
43
            $response = $this->generateResponse($controllerName, $methodName);
44
        } catch (XApiException $xApiException) {
45
            $response = HttpResponse::create('', HttpResponse::HTTP_BAD_REQUEST);
46
        } catch (HttpException $httpException) {
47
            $response = HttpResponse::create(
48
                $httpException->getMessage(),
49
                $httpException->getStatusCode()
50
            );
51
        } catch (Exception $exception) {
52
            $response = HttpResponse::create($exception->getMessage(), HttpResponse::HTTP_BAD_REQUEST);
53
        }
54
55
        $response->headers->set('X-Experience-API-Version', '1.0.3');
56
57
        $response->send();
58
    }
59
60
    /**
61
     * @throws AccessDeniedException
62
     */
63
    private function validateAuth(): bool
64
    {
65
        if (!$this->request->headers->has('Authorization')) {
66
            throw new AccessDeniedException();
67
        }
68
69
        $authHeader = $this->request->headers->get('Authorization');
70
71
        $parts = explode('Basic ', $authHeader, 2);
72
73
        if (empty($parts[1])) {
74
            throw new AccessDeniedException();
75
        }
76
77
        $authDecoded = base64_decode($parts[1]);
78
79
        $parts = explode(':', $authDecoded, 2);
80
81
        if (empty($parts) || 2 !== \count($parts)) {
82
            throw new AccessDeniedException();
83
        }
84
85
        list($username, $password) = $parts;
86
87
        $auth = Database::getManager()
88
            ->getRepository(XApiLrsAuth::class)
89
            ->findOneBy(
90
                ['username' => $username, 'password' => $password, 'enabled' => true]
91
            )
92
        ;
93
94
        if (null == $auth) {
95
            throw new AccessDeniedException();
96
        }
97
98
        return true;
99
    }
100
101
    private function validateVersion(): void
102
    {
103
        $version = $this->request->headers->get('X-Experience-API-Version');
104
105
        if (null === $version) {
106
            throw new BadRequestHttpException('The "X-Experience-API-Version" header is required.');
107
        }
108
109
        if (preg_match('/^1\.0(?:\.\d+)?$/', $version)) {
110
            if ('1.0' === $version) {
111
                $this->request->headers->set('X-Experience-API-Version', '1.0.0');
112
            }
113
114
            return;
115
        }
116
117
        throw new BadRequestHttpException("The xAPI version \"$version\" is not supported.");
118
    }
119
120
    private function getControllerName(): ?string
121
    {
122
        $segments = explode('/', $this->request->getPathInfo());
123
        $segments = array_filter($segments);
124
        $segments = array_values($segments);
125
126
        if (empty($segments)) {
127
            throw new BadRequestHttpException('Bad request');
128
        }
129
130
        $segments = array_map('ucfirst', $segments);
131
        $controllerName = implode('', $segments).'Controller';
132
133
        return "Chamilo\\PluginBundle\\XApi\\Lrs\\$controllerName";
134
    }
135
136
    private function getMethodName(): string
137
    {
138
        $method = $this->request->getMethod();
139
140
        return strtolower($method);
141
    }
142
143
    /**
144
     * @throws AccessDeniedException
145
     */
146
    private function generateResponse(string $controllerName, string $methodName): HttpResponse
147
    {
148
        if (!class_exists($controllerName)
149
            || !method_exists($controllerName, $methodName)
150
        ) {
151
            throw new NotFoundHttpException();
152
        }
153
154
        if (AboutController::class !== $controllerName) {
155
            $this->validateAuth();
156
            $this->validateVersion();
157
        }
158
159
        /** @var HttpResponse $response */
160
        return \call_user_func(
161
            [
162
                new $controllerName($this->request),
163
                $methodName,
164
            ]
165
        );
166
    }
167
168
    private function alternateRequestSyntax(): void
169
    {
170
        if ('POST' !== $this->request->getMethod()) {
171
            return;
172
        }
173
174
        if (null === $method = $this->request->query->get('method')) {
175
            return;
176
        }
177
178
        if ($this->request->query->count() > 1) {
179
            throw new BadRequestHttpException('Including other query parameters than "method" is not allowed. You have to send them as POST parameters inside the request body.');
180
        }
181
182
        $this->request->setMethod($method);
183
        $this->request->query->remove('method');
184
185
        if (null !== $content = $this->request->request->get('content')) {
186
            $this->request->request->remove('content');
187
188
            $this->request->initialize(
189
                $this->request->query->all(),
190
                $this->request->request->all(),
191
                $this->request->attributes->all(),
192
                $this->request->cookies->all(),
193
                $this->request->files->all(),
194
                $this->request->server->all(),
195
                $content
196
            );
197
        }
198
199
        $headerNames = [
200
            'Authorization',
201
            'X-Experience-API-Version',
202
            'Content-Type',
203
            'Content-Length',
204
            'If-Match',
205
            'If-None-Match',
206
        ];
207
208
        foreach ($this->request->request as $key => $value) {
209
            if (\in_array($key, $headerNames, true)) {
210
                $this->request->headers->set($key, $value);
211
            } else {
212
                $this->request->query->set($key, $value);
213
            }
214
215
            $this->request->request->remove($key);
216
        }
217
    }
218
}
219