Completed
Push — master ( edcf20...3ec6c0 )
by Marcel
03:05 queued 01:22
created

src/HttpApi/Controllers/Controller.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\HttpApi\Controllers;
4
5
use Exception;
6
use Pusher\Pusher;
7
use Illuminate\Support\Arr;
8
use Illuminate\Http\Request;
9
use GuzzleHttp\Psr7\Response;
10
use Ratchet\ConnectionInterface;
11
use Illuminate\Http\JsonResponse;
12
use GuzzleHttp\Psr7\ServerRequest;
13
use Illuminate\Support\Collection;
14
use Ratchet\Http\HttpServerInterface;
15
use Psr\Http\Message\RequestInterface;
16
use BeyondCode\LaravelWebSockets\Apps\App;
17
use BeyondCode\LaravelWebSockets\QueryParameters;
18
use Symfony\Component\HttpKernel\Exception\HttpException;
19
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
20
use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
21
22
abstract class Controller implements HttpServerInterface
23
{
24
    /** @var string */
25
    protected $requestBuffer = '';
26
27
    /** @var RequestInterface */
28
    protected $request;
29
30
    /** @var int */
31
    protected $contentLength;
32
33
    /** @var \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager */
34
    protected $channelManager;
35
36
    public function __construct(ChannelManager $channelManager)
37
    {
38
        $this->channelManager = $channelManager;
39
    }
40
41
    public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)
42
    {
43
        $this->request = $request;
44
45
        $this->contentLength = $this->findContentLength($request->getHeaders());
0 ignored issues
show
It seems like $request is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
46
47
        $this->requestBuffer = (string) $request->getBody();
48
49
        $this->checkContentLength($connection);
50
    }
51
52
    protected function findContentLength(array $headers): int
53
    {
54
        return Collection::make($headers)->first(function ($values, $header) {
55
            return strtolower($header) === 'content-length';
56
        });
57
    }
58
59
    public function onMessage(ConnectionInterface $from, $msg)
60
    {
61
        $this->requestBuffer .= $msg;
62
63
        $this->checkContentLength();
0 ignored issues
show
The call to checkContentLength() misses a required argument $connection.

This check looks for function calls that miss required arguments.

Loading history...
64
    }
65
66
    protected function checkContentLength(ConnectionInterface $connection)
67
    {
68
        if (strlen($this->requestBuffer) === $this->contentLength) {
69
            $serverRequest = (new ServerRequest(
70
                $this->request->getMethod(),
71
                $this->request->getUri(),
72
                $this->request->getHeaders(),
73
                $this->requestBuffer,
74
                $this->request->getProtocolVersion()
75
            ))->withQueryParams(QueryParameters::create($this->request)->all());
76
77
            $laravelRequest = Request::createFromBase((new HttpFoundationFactory)->createRequest($serverRequest));
78
79
            $this
80
                ->ensureValidAppId($laravelRequest->appId)
81
                ->ensureValidSignature($laravelRequest);
82
83
            $response = $this($laravelRequest);
84
85
            $connection->send(JsonResponse::create($response));
86
            $connection->close();
87
        }
88
    }
89
90
    public function onClose(ConnectionInterface $connection)
91
    {
92
    }
93
94
    public function onError(ConnectionInterface $connection, Exception $exception)
95
    {
96
        if (! $exception instanceof HttpException) {
97
            return;
98
        }
99
100
        $response = new Response($exception->getStatusCode(), [
101
            'Content-Type' => 'application/json',
102
        ], json_encode([
103
            'error' => $exception->getMessage(),
104
        ]));
105
106
        $connection->send(\GuzzleHttp\Psr7\str($response));
107
108
        $connection->close();
109
    }
110
111
    public function ensureValidAppId(string $appId)
112
    {
113
        if (! App::findById($appId)) {
114
            throw new HttpException(401, "Unknown app id `{$appId}` provided.");
115
        }
116
117
        return $this;
118
    }
119
120
    protected function ensureValidSignature(Request $request)
121
    {
122
        /*
123
         * The `auth_signature` & `body_md5` parameters are not included when calculating the `auth_signature` value.
124
         *
125
         * The `appId`, `appKey` & `channelName` parameters are actually route paramaters and are never supplied by the client.
126
         */
127
        $params = Arr::except($request->query(), ['auth_signature', 'body_md5', 'appId', 'appKey', 'channelName']);
128
129
        if ($request->getContent() !== '') {
130
            $params['body_md5'] = md5($request->getContent());
131
        }
132
133
        ksort($params);
134
135
        $signature = "{$request->getMethod()}\n/{$request->path()}\n".Pusher::array_implode('=', '&', $params);
136
137
        $authSignature = hash_hmac('sha256', $signature, App::findById($request->get('appId'))->secret);
138
139
        if ($authSignature !== $request->get('auth_signature')) {
140
            throw new HttpException(401, 'Invalid auth signature provided.');
141
        }
142
143
        return $this;
144
    }
145
146
    abstract public function __invoke(Request $request);
147
}
148