Completed
Push — master ( caff19...219001 )
by Julien
08:25
created

src/Websocket/Application.php (3 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 Eole\Sandstone\Websocket;
4
5
use Psr\Log\LoggerAwareTrait;
6
use JMS\Serializer\EventDispatcher\EventSubscriberInterface as WrongEventSubscriberInterface;
7
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
8
use Symfony\Component\Security\Core\User\UserInterface;
9
use Ratchet\ConnectionInterface;
10
use Ratchet\Wamp\WampServerInterface;
11
use Eole\Sandstone\Logger\EchoLogger;
12
use Eole\Sandstone\OAuth2\Security\Authentication\Token\OAuth2Token;
13
use Eole\Sandstone\Websocket\Event\ConnectionEvent;
14
use Eole\Sandstone\Websocket\Event\WebsocketAuthenticationEvent;
15
use Eole\Sandstone\Websocket\Event\ConnectionErrorEvent;
16
use Eole\Sandstone\Websocket\Event\WampEvent;
17
use Eole\Sandstone\Websocket\Event\PublishEvent;
18
use Eole\Sandstone\Websocket\Event\RPCEvent;
19
use Eole\Sandstone\Application as SandstoneApplication;
20
21
final class Application implements WampServerInterface
22
{
23
    use LoggerAwareTrait;
24
25
    /**
26
     * @var SandstoneApplication
27
     */
28
    private $sandstoneApplication;
29
30
    /**
31
     * @var Topic[]
32
     */
33
    private $topics;
34
35
    /**
36
     * @param SandstoneApplication $sandstoneApplication
37
     */
38
    public function __construct(SandstoneApplication $sandstoneApplication)
39
    {
40
        $this->sandstoneApplication = $sandstoneApplication;
41
        $this->topics = array();
42
        $this->logger = new EchoLogger();
43
    }
44
45
    /**
46
     * @param ConnectionInterface $conn
47
     *
48
     * @return UserInterface|null
49
     *
50
     * @throws \Exception
51
     */
52
    private function authenticateUser(ConnectionInterface $conn)
53
    {
54
        $accessToken = $conn->WebSocket->request->getQuery()->get('access_token');
55
56
        if (null === $accessToken) {
57
            return null;
58
        }
59
60
        $authenticationManager = $this->sandstoneApplication['security.authentication_manager'];
61
62
        $authenticatedToken = $authenticationManager->authenticate(new OAuth2Token($accessToken));
63
        $user = $authenticatedToken->getUser();
64
        $isUser = $user instanceof UserInterface;
65
66
        if (!$isUser) {
67
            throw new \Exception('User not found.');
68
        }
69
70
        return $user;
71
    }
72
73
    /**
74
     * {@InheritDoc}
75
     */
76
    public function onOpen(ConnectionInterface $conn)
77
    {
78
        $this->dispatch(ConnectionEvent::ON_OPEN, new ConnectionEvent($conn));
79
80
        $this->logger->info('Connection event', ['event' => 'open']);
81
        $this->logger->info('Authentication...');
82
83
        try {
84
            $user = $this->authenticateUser($conn);
85
            if (null === $user) {
86
                $this->logger->info('Anonymous connection');
87
            } else {
88
                $this->dispatch(ConnectionEvent::ON_AUTHENTICATION, new WebsocketAuthenticationEvent($conn, $user));
89
                $this->logger->info('User logged.', ['username' => $user->getUsername()]);
90
            }
91
        } catch (\Exception $e) {
92
            $this->logger->notice('Failed authentication', ['error_message' => $e->getMessage()]);
93
            $conn->send(json_encode('Could not authenticate client, closing connection.'));
94
            $conn->close();
95
96
            return;
97
        }
98
99
        $conn->user = $user;
100
    }
101
102
    /**
103
     * {@InheritDoc}
104
     */
105
    private function getTopic($topicPath)
106
    {
107
        if (!isset($this->topics[$topicPath])) {
108
            $this->topics[$topicPath] = $this->loadTopic($topicPath);
109
        }
110
111
        return $this->topics[$topicPath];
112
    }
113
114
    /**
115
     * @param string $topicPath
116
     *
117
     * @return Topic
118
     */
119
    private function loadTopic($topicPath)
120
    {
121
        $topic = $this->sandstoneApplication['sandstone.websocket.router']->loadTopic($topicPath);
122
123
        if (!$this->sandstoneApplication->offsetExists('serializer')) {
124
            throw new \RuntimeException('A serializer must be registered');
125
        }
126
127
        $topic->setNormalizer($this->sandstoneApplication['serializer']);
128
129
        if ($topic instanceof EventSubscriberInterface) {
130
            $this->sandstoneApplication['dispatcher']->addSubscriber($topic);
131
        }
132
133
        // debug purpose only. Sometimes I use the wrong namespace (JMS one), and it's hard to debug.
134
        if ($topic instanceof WrongEventSubscriberInterface) {
135
            throw new \LogicException(
136
                get_class($topic).' seems to implements the wrong EventSubscriberInterface. '.
137
                'Use the Symfony one, not the JMS one.'
138
            );
139
        }
140
141
        return $topic;
142
    }
143
144
    /**
145
     * {@InheritDoc}
146
     */
147 View Code Duplication
    public function onSubscribe(ConnectionInterface $conn, $topic)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
148
    {
149
        $this->dispatch(ConnectionEvent::ON_SUBSCRIBE, new WampEvent($conn, $topic));
150
151
        $this->logger->info('Topic event', ['event' => 'subscribe', 'topic' => $topic]);
152
153
        $this->getTopic($topic)->onSubscribe($conn, $topic);
154
    }
155
156
    /**
157
     * {@InheritDoc}
158
     */
159
    public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible)
160
    {
161
        $this->dispatch(ConnectionEvent::ON_PUBLISH, new PublishEvent($conn, $topic, $event, $exclude, $eligible));
162
163
        $this->logger->info('Topic event', ['event' => 'publish', 'topic' => $topic]);
164
165
        $this->getTopic($topic)->onPublish($conn, $topic, $event);
166
    }
167
168
    /**
169
     * {@InheritDoc}
170
     */
171 View Code Duplication
    public function onUnSubscribe(ConnectionInterface $conn, $topic)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
172
    {
173
        $this->dispatch(ConnectionEvent::ON_UNSUBSCRIBE, new WampEvent($conn, $topic));
174
175
        $this->logger->info('Topic event', ['event' => 'unsubscribe', 'topic' => $topic]);
176
177
        $this->getTopic($topic)->onUnSubscribe($conn, $topic);
178
    }
179
180
    /**
181
     * {@InheritDoc}
182
     */
183 View Code Duplication
    public function onClose(ConnectionInterface $conn)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
    {
185
        $this->dispatch(ConnectionEvent::ON_CLOSE, new ConnectionEvent($conn));
186
187
        $this->logger->info('Connection event', ['event' => 'close']);
188
189
        foreach ($this->topics as $topic) {
190
            if ($topic->has($conn)) {
191
                $topic->onUnSubscribe($conn, $topic);
192
            }
193
        }
194
    }
195
196
    /**
197
     * {@InheritDoc}
198
     */
199
    public function onCall(ConnectionInterface $conn, $id, $topic, array $params)
200
    {
201
        $this->dispatch(ConnectionEvent::ON_RPC, new RPCEvent($conn, $topic, $id, $params));
202
203
        $this->logger->info('Topic event', ['event' => 'call', 'topic' => $topic]);
204
    }
205
206
    /**
207
     * {@InheritDoc}
208
     */
209
    public function onError(ConnectionInterface $conn, \Exception $e)
210
    {
211
        $this->dispatch(ConnectionEvent::ON_ERROR, new ConnectionErrorEvent($conn, $e));
212
213
        $this->logger->info('Connection event', ['event' => 'error', 'message' => $e->getMessage()]);
214
    }
215
216
    /**
217
     * Dispatch a ConnectionEvent to SandstoneApplication dispatcher.
218
     *
219
     * @param string $eventName
220
     * @param ConnectionEvent $event
221
     */
222
    private function dispatch($eventName, ConnectionEvent $event)
223
    {
224
        $this->sandstoneApplication['dispatcher']->dispatch($eventName, $event);
225
    }
226
}
227