Completed
Push — master ( 53691c...15e6b6 )
by Marcel
01:44
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\Http\Request;
8
use GuzzleHttp\Psr7\Response;
9
use Ratchet\ConnectionInterface;
10
use Illuminate\Http\JsonResponse;
11
use GuzzleHttp\Psr7\ServerRequest;
12
use Illuminate\Support\Collection;
13
use Ratchet\Http\HttpServerInterface;
14
use Psr\Http\Message\RequestInterface;
15
use BeyondCode\LaravelWebSockets\Apps\App;
16
use BeyondCode\LaravelWebSockets\QueryParameters;
17
use Symfony\Component\HttpKernel\Exception\HttpException;
18
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
19
use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
20
21
abstract class Controller implements HttpServerInterface
22
{
23
    /** @var string */
24
    protected $requestBuffer = '';
25
26
    /** @var RequestInterface */
27
    protected $request;
28
29
    /** @var int */
30
    protected $contentLength;
31
32
    /** @var \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager */
33
    protected $channelManager;
34
35
    public function __construct(ChannelManager $channelManager)
36
    {
37
        $this->channelManager = $channelManager;
38
    }
39
40
    public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)
41
    {
42
        $this->request = $request;
43
44
        $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...
45
46
        $this->requestBuffer = (string)$request->getBody();
47
48
        $this->checkContentLength($connection);
49
    }
50
51
    protected function findContentLength(array $headers): int
52
    {
53
        return Collection::make($headers)->first(function ($values, $header) {
54
            return strtolower($header) === 'content-length';
55
        });
56
    }
57
58
    public function onMessage(ConnectionInterface $from, $msg)
59
    {
60
        $this->requestBuffer .= $msg;
61
62
        $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...
63
    }
64
65
    protected function checkContentLength(ConnectionInterface $connection)
66
    {
67
        if (strlen($this->requestBuffer) === $this->contentLength) {
68
            $serverRequest = (new ServerRequest(
69
                $this->request->getMethod(),
70
                $this->request->getUri(),
71
                $this->request->getHeaders(),
72
                $this->requestBuffer,
73
                $this->request->getProtocolVersion()
74
            ))->withQueryParams(QueryParameters::create($this->request)->all());
75
76
            $laravelRequest = Request::createFromBase((new HttpFoundationFactory)->createRequest($serverRequest));
77
78
            $this
79
                ->ensureValidAppId($laravelRequest->appId)
80
                ->ensureValidSignature($laravelRequest);
81
82
            $response = $this($laravelRequest);
83
84
            $connection->send(JsonResponse::create($response));
85
            $connection->close();
86
        }
87
    }
88
89
    public function onClose(ConnectionInterface $connection)
90
    {
91
    }
92
93
    public function onError(ConnectionInterface $connection, Exception $exception)
94
    {
95
        if (! $exception instanceof HttpException) {
96
            return;
97
        }
98
99
        $response = new Response($exception->getStatusCode(), [
100
            'Content-Type' => 'application/json',
101
        ], json_encode([
102
            'error' => $exception->getMessage(),
103
        ]));
104
105
        $connection->send(\GuzzleHttp\Psr7\str($response));
106
107
        $connection->close();
108
    }
109
110
    public function ensureValidAppId(string $appId)
111
    {
112
        if (! App::findById($appId)) {
113
            throw new HttpException(401, "Unknown app id `{$appId}` provided.");
114
        }
115
116
        return $this;
117
    }
118
119
    protected function ensureValidSignature(Request $request)
120
    {
121
        /*
122
         * The `auth_signature` & `body_md5` parameters are not included when calculating the `auth_signature` value.
123
         *
124
         * The `appId`, `appKey` & `channelName` parameters are actually route paramaters and are never supplied by the client.
125
         */
126
        $params = array_except($request->query(), ['auth_signature', 'body_md5', 'appId', 'appKey', 'channelName']);
127
128
        if ($request->getContent() !== '') {
129
            $params['body_md5'] = md5($request->getContent());
130
        }
131
132
        ksort($params);
133
134
        $signature = "{$request->getMethod()}\n/{$request->path()}\n".Pusher::array_implode('=', '&', $params);
135
136
        $authSignature = hash_hmac('sha256', $signature, App::findById($request->get('appId'))->secret);
137
138
        if ($authSignature !== $request->get('auth_signature')) {
139
            throw new HttpException(401, 'Invalid auth signature provided.');
140
        }
141
142
        return $this;
143
    }
144
145
    abstract public function __invoke(Request $request);
146
}
147