diff --git a/README.md b/README.md index efe80d6..0153f97 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,23 @@ That will now run the server ## Advance Profile Feature Support -The current version does not support Advance Profile Features +### Authentication + +| Feature | Static | Dynamic | +|------------|---------|---------| +| Anonymous | ✓ | ✓ | +| Ticket | ✓ | ✓ | +| Wamp-CRA | ✗ | ✗ | +| Wamp-SCRA | ✗ | ✗ | +| Cryptosign | ✗ | ✗ | +| TLS | ✗ | ✗ | +| Cookie | ✗ | ✗ | + +**Additional Authentication** + +- [ ] Add Feature +- [ ] Add checking of role + ## TODOs @@ -123,25 +139,3 @@ The current version does not support Advance Profile Features - [ ] Implement Advance Profile - [ ] Remove Dependencies from Thruway Common - [ ] Add OpenSwoole Table Adapter as Data Provider - -### Authentication - -- [x] Static Anonymous -- [ ] Function Anonymous -- [ ] Dynamic Anonymous -- [x] Static Ticket -- [ ] Function Ticket -- [ ] Dynamic Ticket -- [ ] Static WAMP-CRA -- [ ] Function WAMP-CRA -- [ ] Dynamic WAMP-CRA -- [ ] Static WAMP-SCRA -- [ ] Function WAMP-SCRA -- [ ] Dynamic WAMP-SCRA -- [ ] Static Cryptosign -- [ ] Function Cryptosign -- [ ] Dynamic Cryptosign -- [ ] TLS -- [ ] Cookie -- [ ] Add Feature -- [ ] Add checking of role \ No newline at end of file diff --git a/src/Auth/AbstractDynamicAuthenticator.php b/src/Auth/AbstractDynamicAuthenticator.php new file mode 100644 index 0000000..d7aa94a --- /dev/null +++ b/src/Auth/AbstractDynamicAuthenticator.php @@ -0,0 +1,97 @@ +getDetails(); + $args = [ + 'authmethod' => $this->getMethod(), + ]; + if ($helloDetails->authid) { + $args['authid'] = $helloDetails->authid; + } + if (isset($helloDetails->authextra)) { + $args['authextra'] = $helloDetails->authextra; + } + + $procedureName = $this->config['authenticator']; + $realm = $this->realmManager->getRealm($this->config['authenticator-realm']); + $realmSession = $realm->getMetaSession(); + $requestId = IDHelper::incrementSessionWampID($realmSession); + + $callMessage = new CallMessage($requestId, [], $procedureName, [ + $realm->name, + $args['authid'] ?? null, + array_merge($args, $details), + ]); + + $deferred = new Deferred(); + + $connection = $realmSession->getTransport()->getConnection(); + if ($connection instanceof WithEventDispatcherInterface) { + $connection->once('Message:' . Message::MSG_CALL . ':' . $callMessage->getRequestId(), function (MessageEvent $event): void { + $this->realmManager->dispatch($event->session, $event->message); + }); + $connection->once( + 'Message:' . Message::MSG_RESULT . ':' . $callMessage->getRequestId(), + function (MessageEvent $event) use ($connection, $callMessage, $deferred): void { + $connection->removeListenersForEvent('Message:' . Message::MSG_ERROR . ':' .$callMessage->getRequestId()); + /** @var ResultMessage $message */ + $message = $event->message; + $data = $message->getArguments(); + if (empty($data)) { + $deferred->reject([]); + return; + } + + $result = $data[0]; + $success = $result['status'] ?? true; + + if (!$success) { + $deferred->reject($result); + } else { + $deferred->resolve($result); + } + } + ); + $connection->once( + 'Message:' . Message::MSG_ERROR . ':' . $callMessage->getRequestId(), + function (MessageEvent $event) use ($connection, $callMessage, $deferred): void { + $connection->removeListenersForEvent('Message:' . Message::MSG_RESULT . ':' .$callMessage->getRequestId()); + /** @var ErrorMessage $message */ + $message = $event->message; + + $deferred->resolve([ + 'error_uri' => $message->getErrorURI(), + 'error_details' => $message->getDetails(), + ]); + } + ); + } + + $realmSession->sendMessage($callMessage); + + return $deferred->promise(); + } + + public function setRealmManager(RealmManager $realmManager): void + { + $this->realmManager = $realmManager; + } +} \ No newline at end of file diff --git a/src/Auth/AnonymousDynamicAuthenticator.php b/src/Auth/AnonymousDynamicAuthenticator.php new file mode 100644 index 0000000..80cfc68 --- /dev/null +++ b/src/Auth/AnonymousDynamicAuthenticator.php @@ -0,0 +1,64 @@ +sendMessageToAuthenticator($message, []); + return $promise->then(function ($result) { + $authDetails = []; + if (isset($result['authid'])) { + $authDetails['authid'] = $result['authid']; + } + return [ + 'status' => AuthManager::STATUS_NO_CHALLENGE, + 'auth_details' => $authDetails, + 'verify_details' => $result, + 'challenge_details' => [ + 'challenge_method' => $this->getMethod(), + ], + ]; + }, function ($result) { + $response = [ + 'status' => AuthManager::STATUS_FAILURE, + ]; + if (isset($result['error_uri'])) { + $response['error_uri'] = $result['error_uri']; + } + + if (isset($result['error_details'])) { + $response['error_details'] = $result['error_details']; + } + + return $response; + }); + } + + public function processAuthenticate(Session $session, AuthenticateMessage $message): PromiseInterface + { + return new Promise(function (callable $resolve) { + $resolve(['status' => AuthManager::STATUS_SUCCESS]); + }); + } + + public function getMethod(): string + { + return 'anonymous'; + } + + public function setRealmManager(RealmManager $realmManager): void + { + $this->realmManager = $realmManager; + } +} \ No newline at end of file diff --git a/src/Auth/AuthManager.php b/src/Auth/AuthManager.php index 188cd99..c953746 100644 --- a/src/Auth/AuthManager.php +++ b/src/Auth/AuthManager.php @@ -167,6 +167,9 @@ public function processHelloCurrentAuthenticators(array &$authenticators, Sessio } }); $result = $restPromise->wait(); + + unset($promise); + unset($restPromise); } while ($result); } diff --git a/src/Auth/Exception/AuthenticationException.php b/src/Auth/Exception/AuthenticationException.php new file mode 100644 index 0000000..c51d1fc --- /dev/null +++ b/src/Auth/Exception/AuthenticationException.php @@ -0,0 +1,21 @@ +uri, $code, $previous); + } + + public function getUri(): string + { + return $this->uri; + } + + public function getErrorDetails(): array + { + return $this->errorDetails; + } +} \ No newline at end of file diff --git a/src/Auth/TicketDynamicAuthenticator.php b/src/Auth/TicketDynamicAuthenticator.php index ec6ccfe..2f43acf 100644 --- a/src/Auth/TicketDynamicAuthenticator.php +++ b/src/Auth/TicketDynamicAuthenticator.php @@ -88,11 +88,13 @@ function (MessageEvent $event) use ($connection, $callMessage, $resolve, $authId 'error_details' => $result['error_details'] ?? null, ]); } else { + $authDetails = []; + if (isset($result['authid'])) { + $authDetails['authid'] = $result['authid']; + } $resolve([ 'status' => AuthManager::STATUS_CHALLENGE, - 'auth_details' => [ - 'authid' => $authId, - ], + 'auth_details' => $authDetails, 'verify_details' => $result, 'challenge_details' => [ 'challenge_method' => $this->getMethod(), @@ -123,6 +125,43 @@ function (MessageEvent $event) use ($connection, $callMessage, $resolve): void { public function processAuthenticate(Session $session, AuthenticateMessage $message): PromiseInterface { + return new Promise(function (callable $resolve) use ($session, $message) : void { + $verificationDetails = $session->getAuthenticationDetails()->getVerificationDetails(); + if ($verificationDetails === null) { + $resolve([ + 'status' => AuthManager::STATUS_FAILURE, + 'error_uri' => 'wamp.error.authentication_denied', + 'error_details' => ['message' => 'Invalid ticket / signature'], + ]); + return; + } + $ticket = $verificationDetails->ticket ?? null; + if ($ticket !== $message->getSignature()) { + $resolve([ + 'status' => AuthManager::STATUS_FAILURE, + 'error_uri' => 'wamp.error.authentication_denied', + 'error_details' => ['message' => 'Invalid ticket / signature'], + ]); + return; + } + + $authDetails = [ + 'authid' => $verificationDetails->authid, + ]; + if ($verificationDetails->role) { + $authDetails['authrole'] = $verificationDetails->role; + } + if ($verificationDetails->authextra) { + $authDetails['authextra'] = $verificationDetails->extra; + } + if ($verificationDetails->role) { + $authDetails['authprovider'] = $verificationDetails->authprovider; + } + $resolve([ + 'status' => AuthManager::STATUS_SUCCESS, + 'auth_details' => $authDetails, + ]); + }); } public function getMethod(): string diff --git a/src/Promise/Deferred.php b/src/Promise/Deferred.php index 209ee85..1afa321 100644 --- a/src/Promise/Deferred.php +++ b/src/Promise/Deferred.php @@ -2,7 +2,33 @@ namespace Octamp\Wamp\Promise; -class Deferred extends \Octamp\Client\Promise\Deferred +class Deferred { + private ?PromiseInterface $promise = null; + private mixed $resolveCallback; + private mixed $rejectCallback; + public function promise(): PromiseInterface + { + if ($this->promise === null) { + $this->promise = new Promise(function ($resolve, $reject) { + $this->resolveCallback = $resolve; + $this->rejectCallback = $reject; + }); + } + + return $this->promise; + } + + public function resolve(mixed $value = null): void + { + $this->promise(); + call_user_func($this->resolveCallback, $value); + } + + public function reject(mixed $reason): void + { + $this->promise(); + call_user_func($this->rejectCallback, $reason); + } } \ No newline at end of file diff --git a/src/Promise/Promise.php b/src/Promise/Promise.php index d766243..3745140 100644 --- a/src/Promise/Promise.php +++ b/src/Promise/Promise.php @@ -38,7 +38,7 @@ public function processReject(mixed $value = null): void } } - public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): static { return self::create(function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected) { while ($this->isPending()) { @@ -59,7 +59,7 @@ public function then(?callable $onFulfilled = null, ?callable $onRejected = null }); } - final public function catch(callable $onRejected): PromiseInterface + final public function catch(callable $onRejected): static { return $this->then(null, $onRejected); } @@ -73,7 +73,7 @@ final public function wait(): mixed return $this->result; } - final public static function create(callable $promise): PromiseInterface + final public static function create(callable $promise): static { return new static($promise); } diff --git a/src/Promise/PromiseInterface.php b/src/Promise/PromiseInterface.php index e7a0423..8eefa15 100644 --- a/src/Promise/PromiseInterface.php +++ b/src/Promise/PromiseInterface.php @@ -2,7 +2,25 @@ namespace Octamp\Wamp\Promise; -interface PromiseInterface extends \Octamp\Client\Promise\PromiseInterface +interface PromiseInterface { + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): static; + public function wait(): mixed; + + /** + * This method return a promise with rejected case only + * + * @param callable $onRejected + * @return PromiseInterface + */ + public function catch(callable $onRejected): static; + + /** + * This method create new promise instance + * + * @param callable $promise + * @return PromiseInterface + */ + public static function create(callable $promise): static; } \ No newline at end of file