Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/vonage video shim #354

Merged
merged 6 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,28 @@ $options = [
]
$opentok = new OpenTok($apiKey, $apiSecret, $options);
```
#### Migrating to Vonage Video API

There is some useful behaviour on initialization in this SDK that will help as a stopgap before switching out from the
legacy TokBok API to the new, Vonage Video API. To do this, you can pass in an Application ID for the API Key, and the path to
a Vonage private key as the API Secret.

```php
use OpenTok\OpenTok;
use MyCompany\CustomOpenTokClient;

$applicationID = '61bb2dae-9b69-400c-9abb-642d082af5fc';
$privateKey = './private.key';

$opentok = new OpenTok($applicationID, $privateKey);
```

The SDK will notice that the authentiction has changed, and will automatically start to forward requests to the Vonage API Routes
instead of the OpenTok API Routes. All requests and responses in your code should be exactly the same as they are on the
OpenTok API.

**NOTE:** The SDK will read the private key path from the root directory of your project (generally one level above the docroot
of your application), so you will need to make sure that the path provided is either absolute, or relative to your project root.

### Creating Sessions

Expand Down
2 changes: 1 addition & 1 deletion sample/Archiving/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Next, input your own API Key and API Secret into the `run-demo` script file:

```
export API_KEY=0000000
export API_SECRET=abcdef1234567890abcdef01234567890abcdef
export API_SECRET=b60d0b2568f3ea9731bd9d3f71be263ce19f802f
```

Finally, start the PHP CLI development server (requires PHP >= 5.4) using the `run-demo` script
Expand Down
2 changes: 0 additions & 2 deletions src/OpenTok/Archive.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ public function __construct($archiveData, $options = array())

$this->client = isset($client) ? $client : new Client();
if (!$this->client->isConfigured()) {
Validators::validateApiKey($apiKey);
Validators::validateApiSecret($apiSecret);
Validators::validateApiUrl($apiUrl);

$this->client->configure($apiKey, $apiSecret, $apiUrl);
Expand Down
2 changes: 0 additions & 2 deletions src/OpenTok/ArchiveList.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ public function __construct($archiveListData, $options = array())

$this->client = isset($client) ? $client : new Client();
if (!$this->client->isConfigured()) {
Validators::validateApiKey($apiKey);
Validators::validateApiSecret($apiSecret);
Validators::validateApiUrl($apiUrl);

$this->client->configure($apiKey, $apiSecret, $apiUrl);
Expand Down
2 changes: 0 additions & 2 deletions src/OpenTok/Broadcast.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@ public function __construct($broadcastData, $options = array())
$this->client = $options['client'] ?? new Client();

if (!$this->client->isConfigured()) {
Validators::validateApiKey($options['apiKey']);
Validators::validateApiSecret($options['apiSecret']);
Validators::validateApiUrl($options['apiUrl']);

$this->client->configure($options['apiKey'], $options['apiSecret'], $options['apiUrl']);
Expand Down
29 changes: 23 additions & 6 deletions src/OpenTok/OpenTok.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
* and the API secret for your <a href="https://tokbox.com/account">OpenTok Video API account</a>. Do not
* publicly share your API secret. You will use it with the OpenTok() constructor (only on your web
* server) to create OpenTok sessions.
*
* If you set api_key to a VONAGE_APPLICATION_ID and api_secret to a VONAGE_PRIVATE_KEY_PATH, the SDK
* will hit Vonage Video with Vonage Auth instead.
* <p>
* Be sure to include the entire OpenTok server SDK on your web server.
*/
Expand All @@ -36,26 +39,40 @@ class OpenTok
private $apiSecret;
/** @internal */
private $client;
/** @internal */

/**
* @var bool
* Override to determine whether to hit Vonage servers with Vonage Auth in requests
*/
private $vonage = false;

/**
* @var array
* @internal
*/
public $options;

/** @internal */
public function __construct($apiKey, $apiSecret, $options = array())
{
$apiUrl = 'https://api.opentok.com';

if (Validators::isVonageKeypair($apiKey, $apiSecret)) {
$this->vonage = true;
$apiUrl = 'https://video.api.vonage.com';
}

// unpack optional arguments (merging with default values) into named variables
$defaults = array(
'apiUrl' => 'https://api.opentok.com',
'apiUrl' => $apiUrl,
'client' => null,
'timeout' => null // In the future we should set this to 2
'timeout' => null, // In the future we should set this to 2
);

$this->options = array_merge($defaults, array_intersect_key($options, $defaults));

list($apiUrl, $client, $timeout) = array_values($this->options);

// validate arguments
Validators::validateApiKey($apiKey);
Validators::validateApiSecret($apiSecret);
Validators::validateApiUrl($apiUrl);
Validators::validateClient($client);
Validators::validateDefaultTimeout($timeout);
Expand Down
7 changes: 7 additions & 0 deletions src/OpenTok/Util/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

use OpenTok\Exception\ForceDisconnectAuthenticationException;
use OpenTok\Exception\ForceDisconnectUnexpectedValueException;
use Vonage\JWT\TokenGenerator;

/**
* @internal
Expand Down Expand Up @@ -124,13 +125,19 @@ public function isConfigured()

private function createAuthHeader()
{
if (Validators::isVonageKeypair($this->apiKey, $this->apiSecret)) {
$tokenGenerator = new TokenGenerator($this->apiKey, file_get_contents($this->apiSecret));
return $tokenGenerator->generate();
}

$token = array(
'ist' => 'project',
'iss' => $this->apiKey,
'iat' => time(), // this is in seconds
'exp' => time() + (5 * 60),
'jti' => uniqid('', true),
);

return JWT::encode($token, $this->apiSecret, 'HS256');
}

Expand Down
49 changes: 37 additions & 12 deletions src/OpenTok/Util/Validators.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use OpenTok\Exception\InvalidArgumentException;
use JohnStevenson\JsonWorks\Document;
use JohnStevenson\JsonWorks\Utils as JsonUtils;
use RuntimeException;

/**
* @internal
Expand All @@ -25,13 +26,44 @@ class Validators

public const STREAM_MODES = ['auto', 'manual'];

public static function validateApiKey($apiKey)
public static function isVonageKeypair($apiKey, $apiSecret): bool
{
if (!(is_string($apiKey) || is_int($apiKey))) {
throw new InvalidArgumentException(
'The apiKey was not a string nor an integer: ' . print_r($apiKey, true)
);
if (!is_string($apiKey) || !is_string($apiSecret)) {
throw new InvalidArgumentException("API Key and API Secret must be strings.");
}

$isOpenTokKey = preg_match('/^\d+$/', $apiKey);
$isOpenTokSecret = preg_match('/^[a-f0-9]{40}$/i', $apiSecret);

if ($isOpenTokKey && $isOpenTokSecret) {
return false;
}

$isVonageApplicationId = preg_match('/^[a-f0-9\-]{36}$/i', $apiKey);
$isVonagePrivateKey = self::isValidPrivateKey($apiSecret);

if ($isVonageApplicationId && $isVonagePrivateKey) {
return true;
}

// Mixed formats or invalid formats - throw an exception
throw new InvalidArgumentException("Invalid Vonage Keypair credentials provided.");
}

private static function isValidPrivateKey(string $filePath): bool
{
if (!file_exists($filePath) || !is_readable($filePath)) {
throw new InvalidArgumentException("Private key file does not exist or is not readable.");
}

$keyContents = file_get_contents($filePath);

if ($keyContents === false) {
throw new RuntimeException("Failed to read private key file.");
}

// Check if it contains a valid private RSA key header
return (bool) preg_match('/^-----BEGIN PRIVATE KEY-----[\s\S]+-----END PRIVATE KEY-----$/m', trim($keyContents));
}

public static function validateForceMuteAllOptions(array $options)
Expand All @@ -56,13 +88,6 @@ public static function validateForceMuteAllOptions(array $options)
}
}

public static function validateApiSecret($apiSecret)
{
if (!(is_string($apiSecret))) {
throw new InvalidArgumentException('The apiSecret was not a string: ' . print_r($apiSecret, true));
}
}

public static function validateApiUrl($apiUrl)
{
if (!(is_string($apiUrl) && filter_var($apiUrl, FILTER_VALIDATE_URL))) {
Expand Down
2 changes: 1 addition & 1 deletion tests/OpenTokTest/ArchiveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function setupArchives($streamMode)
private function setupOTWithMocks($mocks)
{
$this->API_KEY = defined('API_KEY') ? API_KEY : '12345678';
$this->API_SECRET = defined('API_SECRET') ? API_SECRET : '0123456789abcdef0123456789abcdef0123456789';
$this->API_SECRET = defined('API_SECRET') ? API_SECRET : 'b60d0b2568f3ea9731bd9d3f71be263ce19f802f';

if (is_array($mocks)) {
$responses = TestHelpers::mocksToResponses($mocks, self::$mockBasePath);
Expand Down
35 changes: 1 addition & 34 deletions tests/OpenTokTest/BroadcastTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function setupBroadcasts($streamMode)
private function setupOTWithMocks($mocks)
{
$this->API_KEY = defined('API_KEY') ? API_KEY : '12345678';
$this->API_SECRET = defined('API_SECRET') ? API_SECRET : '0123456789abcdef0123456789abcdef0123456789';
$this->API_SECRET = defined('API_SECRET') ? API_SECRET : 'b60d0b2568f3ea9731bd9d3f71be263ce19f802f';

if (is_array($mocks)) {
$responses = TestHelpers::mocksToResponses($mocks, self::$mockBasePath);
Expand Down Expand Up @@ -98,39 +98,6 @@ private function setupOTWithMocks($mocks)
$handlerStack->push($history);
}

public function testCannotCreateBroadcastWithAddInvalidApiKey(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The apiKey was not a string nor an integer: ');

$broadcastObject = new Broadcast($this->broadcastData, [
'apiKey' => new Client()
]);
}

public function testCannotCreateBroadcastWithInvalidApiSecret(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The apiSecret was not a string: OpenTok\Util\Client Object');

$broadcastObject = new Broadcast($this->broadcastData, [
'apiKey' => 'test',
'apiSecret' => new Client()
]);
}

public function testCannotCreateBroadcastWithInvalidApiUrl(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The optional apiUrl was not a string: ');

$broadcastObject = new Broadcast($this->broadcastData, [
'apiKey' => 'validKey',
'apiSecret' => 'validSecret',
'apiUrl' => 'test'
]);
}

private function setupOT()
{
return $this->setupOTWithMocks([]);
Expand Down
Loading
Loading