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

Application::onOpen()   B

Complexity

Conditions 3
Paths 6

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 17
nc 6
nop 1
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');
0 ignored issues
show
Bug introduced by
Accessing WebSocket on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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;
0 ignored issues
show
Bug introduced by
Accessing user on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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
Duplication introduced by
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);
0 ignored issues
show
Compatibility introduced by
$conn of type object<Ratchet\ConnectionInterface> is not a sub-type of object<Ratchet\Wamp\WampConnection>. It seems like you assume a concrete implementation of the interface Ratchet\ConnectionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like $topic defined by parameter $topic on line 147 can also be of type object<Ratchet\Wamp\Topic>; however, Eole\Sandstone\Websocket\Topic::onSubscribe() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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);
0 ignored issues
show
Compatibility introduced by
$conn of type object<Ratchet\ConnectionInterface> is not a sub-type of object<Ratchet\Wamp\WampConnection>. It seems like you assume a concrete implementation of the interface Ratchet\ConnectionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like $topic defined by parameter $topic on line 159 can also be of type object<Ratchet\Wamp\Topic>; however, Eole\Sandstone\Websocket\Topic::onPublish() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
166
    }
167
168
    /**
169
     * {@InheritDoc}
170
     */
171 View Code Duplication
    public function onUnSubscribe(ConnectionInterface $conn, $topic)
0 ignored issues
show
Duplication introduced by
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);
0 ignored issues
show
Compatibility introduced by
$conn of type object<Ratchet\ConnectionInterface> is not a sub-type of object<Ratchet\Wamp\WampConnection>. It seems like you assume a concrete implementation of the interface Ratchet\ConnectionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like $topic defined by parameter $topic on line 171 can also be of type object<Ratchet\Wamp\Topic>; however, Eole\Sandstone\Websocket\Topic::onUnSubscribe() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
178
    }
179
180
    /**
181
     * {@InheritDoc}
182
     */
183 View Code Duplication
    public function onClose(ConnectionInterface $conn)
0 ignored issues
show
Duplication introduced by
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);
0 ignored issues
show
Compatibility introduced by
$conn of type object<Ratchet\ConnectionInterface> is not a sub-type of object<Ratchet\Wamp\WampConnection>. It seems like you assume a concrete implementation of the interface Ratchet\ConnectionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
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