Skip to content

Commit

Permalink
feat: add dynamic anonymous authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
cydrickn committed Jul 14, 2024
1 parent 1d33f5c commit 5cf743f
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 31 deletions.
40 changes: 17 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,33 +115,27 @@ 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

- [ ] Implement CBOR Serializer https://wamp-proto.org/wamp_bp_latest_ietf.html#name-serializers
- [ ] 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
97 changes: 97 additions & 0 deletions src/Auth/AbstractDynamicAuthenticator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace Octamp\Wamp\Auth;

use Octamp\Wamp\Connection\WithEventDispatcherInterface;
use Octamp\Wamp\Helper\IDHelper;
use Octamp\Wamp\Promise\Deferred;
use Octamp\Wamp\Promise\PromiseInterface;
use Octamp\Wamp\Realm\RealmManager;
use Octamp\Wamp\Session\Event\MessageEvent;
use Thruway\Message\CallMessage;
use Thruway\Message\HelloMessage;
use Thruway\Message\Message;
use Thruway\Message\ResultMessage;

abstract class AbstractDynamicAuthenticator extends AbstractAuthenticator implements WithRealmManagerInterface
{
protected ?RealmManager $realmManager;

protected function sendMessageToAuthenticator(HelloMessage $message, array $details = []): PromiseInterface
{
$helloDetails = $message->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;
}
}
64 changes: 64 additions & 0 deletions src/Auth/AnonymousDynamicAuthenticator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Octamp\Wamp\Auth;

use Octamp\Wamp\Promise\Promise;
use Octamp\Wamp\Promise\PromiseInterface;
use Octamp\Wamp\Realm\RealmManager;
use Octamp\Wamp\Session\Session;
use Thruway\Message\AuthenticateMessage;
use Thruway\Message\HelloMessage;

class AnonymousDynamicAuthenticator extends AbstractDynamicAuthenticator
{
protected ?RealmManager $realmManager;

public function processHello(Session $session, HelloMessage $message): PromiseInterface
{
$promise = $this->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;
}
}
3 changes: 3 additions & 0 deletions src/Auth/AuthManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ public function processHelloCurrentAuthenticators(array &$authenticators, Sessio
}
});
$result = $restPromise->wait();

unset($promise);
unset($restPromise);
} while ($result);
}

Expand Down
21 changes: 21 additions & 0 deletions src/Auth/Exception/AuthenticationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Octamp\Wamp\Auth\Exception;

class AuthenticationException extends \Exception
{
public function __construct(protected string $uri, protected array $errorDetails, ?string $message = null, int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message ?? $this->uri, $code, $previous);
}

public function getUri(): string
{
return $this->uri;
}

public function getErrorDetails(): array
{
return $this->errorDetails;
}
}
45 changes: 42 additions & 3 deletions src/Auth/TicketDynamicAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
28 changes: 27 additions & 1 deletion src/Promise/Deferred.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
6 changes: 3 additions & 3 deletions src/Promise/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down
Loading

0 comments on commit 5cf743f

Please sign in to comment.