/** * @param SessionEvent $event */ public function onOpen(SessionEvent $event) { /* @var $mapping \Voryx\ThruwayBundle\Mapping\URIClassMapping */ foreach ($event->getResourceMappings() as $name => $mapping) { $annotation = $mapping->getAnnotation(); if (!$annotation instanceof Register) { continue; } $topicStateHandler = $annotation->getTopicStateHandlerFor(); if (!$topicStateHandler) { continue; } $session = $event->getSession(); $registration = new \stdClass(); $registration->handler_uri = $annotation->getName(); $registration->uri = $annotation->getTopicStateHandlerFor(); $registration->options = $annotation->getTopicStateHandlerOptions(); //Register Topic Handlers $registration->topic = $topicStateHandler; $session->call('add_state_handler', [$registration])->then(function ($res) use($annotation) { Logger::info($this, "Registered topic handler RPC: '{$annotation->getName()}'' for topic: '{$annotation->getTopicStateHandlerFor()}'"); }, function (ErrorMessage $error) use($annotation) { Logger::error($this, "Unable to register topic handler RPC: '{$annotation->getName()}'' for topic: '{$annotation->getTopicStateHandlerFor()}'' Error: '{$error->getErrorURI()}''"); }); } }
public function handleRouterStart(RouterStartEvent $event) { $server = new Server($this->bindAddress, $this->port, false, ["wamp.2.json"]); Logger::info($this, "Websocket listening on " . $this->bindAddress . ":" . $this->port); $this->serverDisposable = $server->subscribe(new CallbackObserver(function (MessageSubject $ms) { $this->createNewSessionForMessageSubject($ms); }, function (\Exception $err) { Logger::error($this, "Received error on server: " . $err->getMessage()); }, function () { Logger::alert($this, "Completed. Not sure if we should ever do that."); })); }
/** * Gets and published the topics state to this subscription * * @param Subscription $subscription * @return mixed */ public function publishState(Subscription $subscription) { //Pause all non-state building event messages $subscription->pauseForState(); $sessionId = $subscription->getSession()->getSessionId(); $this->clientSession->call($this->getProcedureName(), [$subscription->getUri(), $sessionId, $subscription->getOptions(), $subscription->getSession()->getAuthenticationDetails()])->then(function ($res) use($subscription) { $pubId = null; if (isset($res[0])) { $pubId = $res[0]; } $subscription->unPauseForState($pubId); }, function ($error) use($subscription) { Logger::error($this, "Could not call '{$this->getProcedureName()}' when restoring state"); $subscription->unPauseForState(); }); }
/** * This should take a authid string as the argument and return * an associative array with authid, key, and salt. * * If salt is non-null, the key is the salted version of the password. * * @param $authid * @throws \Exception * @return array */ public function get($authid) { try { $userProvider = $this->container->getParameter('voryx_thruway')['user_provider']; if (null === $userProvider) { throw new \Exception('voryx_thruway.user_provider must be set.'); } $user = $this->container->get($userProvider)->loadUserByUsername($authid); if (!$user) { throw new \Exception("Can't log in, bad credentials"); } return ["user" => $user->getUsername(), "key" => $user->getPassword(), "salt" => $user->getSalt()]; } catch (\Exception $e) { Logger::error($this, $e->getMessage()); return false; } }
/** * Get Authenticate message from challenge message * * @param \Thruway\Message\ChallengeMessage $msg * @return \Thruway\Message\AuthenticateMessage|boolean */ public function getAuthenticateFromChallenge(ChallengeMessage $msg) { Logger::info($this, "Got challenge"); Logger::debug($this, "Challenge Message: " . json_encode($msg)); if (!in_array($msg->getAuthMethod(), $this->getAuthMethods())) { //throw new \Exception("method isn't in methods"); return false; } if (!is_array($msg->getDetails())) { Logger::info($this, "No details sent with challenge"); return false; } $challenge = ''; if (isset($msg->getDetails()['challenge'])) { $challenge = $msg->getDetails()['challenge']; } else { Logger::info($this, "No challenge for wampcra?"); return false; } $keyToUse = $this->key; if (isset($msg->getDetails()['salt'])) { // we need a salted key $salt = $msg->getDetails()['salt']; $keyLen = 32; if (isset($msg->getDetails()['keylen'])) { if (is_numeric($msg->getDetails()['keylen'])) { $keyLen = $msg->getDetails()['keylen']; } else { Logger::error($this, "keylen is not numeric."); } } $iterations = 1000; if (isset($msg->getDetails()['iterations'])) { if (is_numeric($msg->getDetails()['iterations'])) { $iterations = $msg->getDetails()['iterations']; } else { Logger::error($this, "iterations is not numeric."); } } $keyToUse = $this->getDerivedKey($this->key, $salt, $iterations, $keyLen); } $token = base64_encode(hash_hmac('sha256', $challenge, $keyToUse, true)); $authMessage = new AuthenticateMessage($token); Logger::debug($this, "returning: " . json_encode($authMessage)); return $authMessage; }
/** * Handle process reveived data * * @param mixed $data * @return void */ public function handleData($data) { // if ($this->handshakeByte == 0) { // $this->handshakeByte = $data[0]; // $data = substr($data, 1); // } $this->buffer = $this->buffer . $data; $bufferLen = strlen($this->buffer); while ($bufferLen > 0 && $bufferLen >= $this->msgLen) { if ($this->msgLen == 0) { // the next 4 bytes are going to be the msglen if ($bufferLen >= 4) { $this->msgLen = array_values(unpack("N", $this->buffer))[0]; if ($this->msgLen <= 0) { Logger::error("Invalid message size sent"); $this->close(); } // shift off the first 4 bytes $bufferLen = $bufferLen - 4; $this->buffer = substr($this->buffer, 4, $bufferLen); } else { // we don't have enough to get the message length return; } } if ($bufferLen >= $this->msgLen) { $msg = $this->getSerializer()->deserialize(substr($this->buffer, 0, $this->msgLen)); //$this->peer->onMessage($this, $msg); $this->emit("message", [$this, $msg]); if ($bufferLen == $this->msgLen) { $this->buffer = ""; $this->msgLen = 0; $bufferLen = 0; } else { $bufferLen = $bufferLen - $this->msgLen; $this->buffer = substr($this->buffer, $this->msgLen, $bufferLen); $this->msgLen = 0; } } } }
/** * @param string $handlerUri * @throws \Exception */ public function setStateHandler($handlerUri) { if (!Utils::uriIsValid($handlerUri)) { Logger::error($this, "Invalid URI"); throw new \InvalidArgumentException("Invalid URI"); } $this->stateHandler = $handlerUri; }
/** * process unregister * * @param \Thruway\ClientSession $session * @param string $Uri * @throws \Exception * @return \React\Promise\Promise|false */ public function unregister(ClientSession $session, $Uri) { // TODO: maybe add an option to wait for pending calls to finish $registration = null; foreach ($this->registrations as $k => $r) { if (isset($r['procedure_name'])) { if ($r['procedure_name'] == $Uri) { $registration =& $this->registrations[$k]; break; } } } if ($registration === null) { Logger::warning($this, "registration not found: " . $Uri); return false; } // we remove the callback from the client here // because we don't want the client to respond to any more calls $registration['callback'] = null; $futureResult = new Deferred(); if (!isset($registration["registration_id"])) { // this would happen if the registration was never acknowledged by the router // we should remove the registration and resolve any pending deferreds Logger::error($this, "Registration ID is not set while attempting to unregister " . $Uri); // reject the pending registration $registration['futureResult']->reject(); // TODO: need to figure out what to do in this off chance // We should still probably return a promise here that just rejects // there is an issue with the pending registration too that // the router may have a "REGISTERED" in transit and may still think that is // good to go - so maybe still send the unregister? } $requestId = Session::getUniqueId(); // save the request id so we can find this in the registration // list to call the deferred and remove it from the list $registration['unregister_request_id'] = $requestId; $registration['unregister_deferred'] = $futureResult; $unregisterMsg = new UnregisterMessage($requestId, $registration['registration_id']); $session->sendMessage($unregisterMsg); return $futureResult->promise(); }
/** * This creates a specific error message depending on the message we are reporting * an error on. * * @param \Thruway\Message\Message $msg * @param string $errorUri * @return \Thruway\Message\ErrorMessage */ public static function createErrorMessageFromMessage(Message $msg, $errorUri = null) { if ($errorUri === null) { $errorUri = "wamp.error.unknown"; } if (method_exists($msg, "getRequestId")) { return new ErrorMessage($msg->getMsgCode(), $msg->getRequestId(), new \stdClass(), $errorUri); } Logger::error(null, "Can't send an error message because the message didn't not have a request id "); }
/** * Call the handler that was registered to handle the Authenticate Message * * @param $authMethod * @param $authMethodInfo * @param Realm $realm * @param Session $session * @param AuthenticateMessage $msg */ private function onAuthenticateHandler($authMethod, $authMethodInfo, Realm $realm, Session $session, AuthenticateMessage $msg) { $onAuthenticateSuccess = function ($res) use($realm, $session) { if (count($res) < 1) { $session->abort(new \stdClass(), "thruway.error.authentication_failure"); return; } // we should figure out a way to have the router send the welcome // message so that the roles and extras that go along with it can be // filled in if ($res[0] == "SUCCESS") { $welcomeDetails = new \stdClass(); if (isset($res[1]->authid)) { $session->getAuthenticationDetails()->setAuthId($res[1]->authid); } else { $session->getAuthenticationDetails()->setAuthId('authenticated_user'); } $authRole = 'authenticated_user'; $session->getAuthenticationDetails()->addAuthRole($authRole); if (isset($res[1]->authroles)) { $session->getAuthenticationDetails()->addAuthRole($res[1]->authroles); } if (isset($res[1]->authrole)) { $session->getAuthenticationDetails()->addAuthRole($res[1]->authrole); } if (isset($res[1]->_thruway_authextra)) { $session->getAuthenticationDetails()->setAuthExtra($res[1]->_thruway_authextra); } if (isset($res[1]) && is_object($res[1])) { $res[1]->authrole = $session->getAuthenticationDetails()->getAuthRole(); $res[1]->authroles = $session->getAuthenticationDetails()->getAuthRoles(); $res[1]->authid = $session->getAuthenticationDetails()->getAuthId(); foreach ($res[1] as $k => $v) { $welcomeDetails->{$k} = $v; } } $session->setAuthenticated(true); $session->sendMessage(new WelcomeMessage($session->getSessionId(), $welcomeDetails)); } else { $session->abort(new \stdClass(), "thruway.error.authentication_failure"); } }; $onAuthenticateError = function () use($session) { Logger::error($this, "onauthenticate rejected the promise"); $session->abort("thruway.error.unknown"); }; $extra = new \stdClass(); $extra->challenge_details = $session->getAuthenticationDetails()->getChallengeDetails(); $arguments = new \stdClass(); $arguments->extra = $extra; $arguments->authid = $session->getAuthenticationDetails()->getAuthId(); $arguments->challenge = $session->getAuthenticationDetails()->getChallenge(); $arguments->signature = $msg->getSignature(); $arguments->authmethod = $authMethod; $arguments->hello_message = $session->getHelloMessage(); // now we send our authenticate information to the RPC $onAuthenticateHandler = $authMethodInfo['handlers']->onauthenticate; $this->session->call($onAuthenticateHandler, [$arguments])->then($onAuthenticateSuccess, $onAuthenticateError); }
/** * Process AuthenticateMessage * * @param \Thruway\Session $session * @param \Thruway\Message\AuthenticateMessage $msg */ private function processAuthenticate(Session $session, AuthenticateMessage $msg) { if ($this->getAuthenticationManager() !== null) { try { $this->getAuthenticationManager()->onAuthenticationMessage($this, $session, $msg); } catch (\Exception $e) { $session->abort(new \stdClass(), "thruway.error.internal"); Logger::error($this, "Authenticate sent to realm without auth manager."); } } else { $session->abort(new \stdClass(), "thruway.error.internal"); Logger::error($this, "Authenticate sent to realm without auth manager."); } }
/** * Process AuthenticateMessage * * @param \Thruway\Session $session * @param \Thruway\Message\AuthenticateMessage $msg */ private function processAuthenticate(Session $session, AuthenticateMessage $msg) { $session->abort(new \stdClass(), "thruway.error.internal"); Logger::error($this, "Authenticate sent to realm without auth manager."); }
/** * Process InvocationError * * @param \Thruway\Session $session * @param \Thruway\Message\ErrorMessage $msg */ private function processInvocationError(Session $session, ErrorMessage $msg) { //$call = $this->getCallByRequestId($msg->getRequestId()); $call = $this->callInvocationIndex[$msg->getRequestId()]; if (!$call) { $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg); Logger::error($this, 'No call for invocation error message: ' . $msg->getRequestId()); // TODO: do we send a message back to the callee? $errorMsg->setErrorURI('wamp.error.no_such_procedure'); $session->sendMessage($errorMsg); return; } if ($call->getCalleeSession() !== $session) { Logger::error($this, "Attempted Invocation Error from session that does not own the call"); return; } $call->getRegistration()->removeCall($call); $this->removeCall($call); $errorMsg = ErrorMessage::createErrorMessageFromMessage($call->getCallMessage()); $errorMsg->setErrorURI($msg->getErrorURI()); $errorMsg->setArguments($msg->getArguments()); $errorMsg->setArgumentsKw($msg->getArgumentsKw()); // not sure if this detail should pass through $errorMsg->setDetails($msg->getDetails()); $call->getCallerSession()->sendMessage($errorMsg); }
/** * process unsubscribed * * @param \Thruway\ClientSession $session * @param \Thruway\Message\UnsubscribedMessage $msg */ protected function processUnsubscribed(ClientSession $session, UnsubscribedMessage $msg) { foreach ($this->subscriptions as $key => $subscription) { if (isset($subscription['unsubscribed_request_id']) && $subscription['unsubscribed_request_id'] == $msg->getRequestId()) { /* @var $deferred \React\Promise\Deferred */ $deferred = $subscription['unsubscribed_deferred']; $deferred->resolve(); unset($this->subscriptions[$key]); return; } } Logger::error($this, "Got an Unsubscribed Message, but couldn't find corresponding request.\n"); }
/** * Handle Authenticate message * * @param \Thruway\Realm $realm * @param \Thruway\Session $session * @param \Thruway\Message\AuthenticateMessage $msg * @throws \Exception */ public function handleAuthenticateMessage(Realm $realm, Session $session, AuthenticateMessage $msg) { if ($session->getAuthenticationDetails() === null) { throw new \Exception('Authenticate with no previous auth details'); } $authMethod = $session->getAuthenticationDetails()->getAuthMethod(); // find the auth method foreach ($this->authMethods as $am => $authMethodInfo) { if ($authMethod == $am) { // found it // now we send our authenticate information to the RPC $this->getCaller()->call($this->session, $authMethodInfo['handlers']['onauthenticate'], ['authmethod' => $authMethod, 'challenge' => $session->getAuthenticationDetails()->getChallenge(), 'extra' => ['challenge_details' => $session->getAuthenticationDetails()->getChallengeDetails()], 'signature' => $msg->getSignature(), 'authid' => $session->getAuthenticationDetails()->getAuthId()])->then(function ($res) use($session) { // if (!is_array($res)) { // return; // } if (count($res) < 1) { return; } // we should figure out a way to have the router send the welcome // message so that the roles and extras that go along with it can be // filled in if ($res[0] == "SUCCESS") { $welcomeDetails = ["roles" => []]; if (isset($res[1]) && isset($res[1]['authid'])) { $session->getAuthenticationDetails()->setAuthId($res[1]['authid']); } else { $session->getAuthenticationDetails()->setAuthId('authenticated_user'); $res[1]['authid'] = $session->getAuthenticationDetails()->getAuthId(); } $authRole = 'authenticated_user'; $session->getAuthenticationDetails()->addAuthRole($authRole); if (isset($res[1]) && isset($res[1]['authroles'])) { $session->getAuthenticationDetails()->addAuthRole($res[1]['authroles']); $authRole = $session->getAuthenticationDetails()->getAuthRole(); } if (isset($res[1]) && isset($res[1]['authrole'])) { $session->getAuthenticationDetails()->addAuthRole($res[1]['authrole']); } if (isset($res[1])) { $res[1]['authrole'] = $session->getAuthenticationDetails()->getAuthRole(); $res[1]['authroles'] = $session->getAuthenticationDetails()->getAuthRoles(); $res[1]['authid'] = $session->getAuthenticationDetails()->getAuthId(); if (is_array($res[1])) { $welcomeDetails = array_merge($welcomeDetails, $res[1]); } } $session->setAuthenticated(true); $session->sendMessage(new WelcomeMessage($session->getSessionId(), $welcomeDetails)); } else { $session->abort(new \stdClass(), "bad.login"); } }, function () use($session) { Logger::error($this, "onauthenticate rejected the promise"); $session->abort("thruway.error.unknown"); }); } } }
/** @inheritdoc */ public function onError(ConnectionInterface $conn, \Exception $e) { Logger::error($this, "onError..."); // TODO: Implement onError() method. }
/** * Process InvocationError * * @param \Thruway\Session $session * @param \Thruway\Message\ErrorMessage $msg * @return boolean|void */ private function processInvocationError(Session $session, ErrorMessage $msg) { $call = $this->getCallByRequestId($msg->getRequestId()); if (!$call) { $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg); Logger::error($this, 'No call for invocation error message: ' . $msg->getRequestId()); // TODO: do we send a message back to the callee? $errorMsg->setErrorURI('wamp.error.no_such_procedure'); $session->sendMessage($errorMsg); return false; } $call->getRegistration()->removeCall($call); $errorMsg = ErrorMessage::createErrorMessageFromMessage($call->getCallMessage()); $errorMsg->setErrorURI($msg->getErrorURI()); $errorMsg->setArguments($msg->getArguments()); $errorMsg->setArgumentsKw($msg->getArgumentsKw()); $call->getCallerSession()->sendMessage($errorMsg); }