Passed
Push — 1.11.x ( 106594...56cdc0 )
by Angel Fernando Quiroz
08:52
created

LrsRequest::isValidVersion()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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