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 2 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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ $options = [
'client' => new CustomOpenTokClient(),
'timeout' => 10,
]
$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 add a `private_key_path` and
`application_id` into the `$options`. Note that the SDK will read the private key path from the root directory of
the SDK, so you will need to adjust the directory structures accordingly.

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

$options = [
'application_id' => 'your_application_id',
'private_key_path' => './path-to-your.key'
]

$opentok = new OpenTok($apiKey, $apiSecret, $options);
```

Expand Down
40 changes: 34 additions & 6 deletions src/OpenTok/OpenTok.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,47 @@ class OpenTok
private $apiSecret;
/** @internal */
private $client;
/** @internal */

/**
* @var array
* @internal
* Options that can override the defaults. Additionally, you can set the keys
* application_id & private_key_path to strings that will then override the default
* OpenTok Client behaviour when making requests to the Vonage Video API.
*/
public $options;

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

if (array_key_exists('application_id', $options) && array_key_exists('private_key_path', $options)) {
$validateKeys = false;
$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
'application_id' => null,
'private_key_path' => null,
);

$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);
if ($validateKeys) {
Validators::validateApiKey($apiKey);
Validators::validateApiSecret($apiSecret);
}

Validators::validateApiUrl($apiUrl);
Validators::validateClient($client);
Validators::validateDefaultTimeout($timeout);
Expand Down Expand Up @@ -116,9 +137,16 @@ public function __construct($apiKey, $apiSecret, $options = array())
* @param bool $legacy By default, OpenTok uses SHA256 JWTs for authentication. Switching
* legacy to true will create a deprecated T1 token for backwards compatibility.
*
* Optionally, you can set $vonage to true and it will generate a Vonage Video token if you are using
* the shim behaviour.
*
* @return string The token string.
*/
public function generateToken(string $sessionId, array $options = array(), bool $legacy = false): string
public function generateToken(
string $sessionId,
array $options = array(),
bool $legacy = false
): string
{
// Note, JWT generation disabled due to a backend bug regarding `exp` claims being mandatory - CRT
// if ($legacy) {
Expand Down
20 changes: 20 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 All @@ -46,6 +47,8 @@ class Client

protected $apiKey;
protected $apiSecret;
protected $applicationId = null;
protected $privateKeyPath = null;
protected $configured = false;

/**
Expand All @@ -64,6 +67,14 @@ public function configure($apiKey, $apiSecret, $apiUrl, $options = array())
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;

if (array_key_exists('application_id', $options) || array_key_exists('private_key_path', $options)) {
if (!is_null($options['application_id']) && !is_null($options['private_key_path'])) {
$this->applicationId = $options['application_id'];
$this->privateKeyPath = $options['private_key_path'];
$apiUrl = 'https://video.api.vonage.com';
}
}

if (isset($this->options['client'])) {
$this->client = $options['client'];
} else {
Expand Down Expand Up @@ -124,13 +135,22 @@ public function isConfigured()

private function createAuthHeader()
{
if (!is_null($this->applicationId) && !is_null($this->privateKeyPath)) {
$projectRoot = dirname(__DIR__, 3); // Adjust the number of dirname() calls if necessary to match your
// project structure.
$privateKeyFullPath = $projectRoot . DIRECTORY_SEPARATOR . $this->privateKeyPath;
$tokenGenerator = new TokenGenerator($this->applicationId, file_get_contents($privateKeyFullPath));
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
18 changes: 18 additions & 0 deletions src/OpenTok/Util/Validators.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ public static function validateApiKey($apiKey)
}
}

public static function validateVonageJwtArguments(array $options)
{
if (!isset($data['application_id']) && !isset($data['private_key_path'])) {
return;
}

if (isset($data['application_id']) && isset($data['private_key_path'])) {
if (is_string($data['application_id']) && is_string($data['private_key_path'])) {
return;
};
}

// If one key is present but not the other, validation fails
throw new InvalidArgumentException(
'You are attempting to use the Vonage Video API. Both application_id and private key paths must be in options and both strings.'
);
}

public static function validateForceMuteAllOptions(array $options)
{
$validOptions = [
Expand Down
32 changes: 30 additions & 2 deletions tests/OpenTokTest/OpenTokTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -758,9 +758,37 @@ public function testWillCreateLegacyT1DirectlyToBypassExpBug(): void
$this->assertEquals('T1', substr($token, 0, 2));
}

public function testWillHitVonageVideoWithVonageJwt(): void
{
// All of this manual DRY exists because the hardcoded setup helper methods
// were not written to handle this sort of behaviour overriding.
$mocks = [
'code' => 200,
'headers' => [
'Content-Type' => 'application/json'
],
'path' => 'v2/project/APIKEY/archive/session'
];

$customAgent = [
'application_id' => 'abc123',
'private_key_path' => './tests/OpenTokTest/test.key',
];

$this->setupOTWithMocks([$mocks], $customAgent);

$sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';
$archive = $this->opentok->startArchive($sessionId, ['maxBitrate' => 2000000]);

$this->assertCount(1, $this->historyContainer);

$request = $this->historyContainer[0]['request'];
$this->assertEquals('POST', strtoupper($request->getMethod()));
}

/**
* Makes sure that a JWT is generated for the client-side token
*
*
* Currently disabled due to the backend requiring an `exp` claim, which was
* not required on T1s. Uncomment when the backend is fixed. - CRT
*/
Expand Down Expand Up @@ -1319,7 +1347,7 @@ public function testGetsArchiveWithMaxBitrate(): void

$request = $this->historyContainer[0]['request'];
$this->assertEquals('GET', strtoupper($request->getMethod()));
$this->assertEquals('/v2/project/'.$this->API_KEY.'/archive/'.$archiveId, $request->getUri()->getPath());
$this->assertEquals('/v2/project/' . $this->API_KEY . '/archive/' . $archiveId, $request->getUri()->getPath());
$this->assertEquals('api.opentok.com', $request->getUri()->getHost());
$this->assertEquals('https', $request->getUri()->getScheme());

Expand Down
28 changes: 28 additions & 0 deletions tests/OpenTokTest/test.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCy3YiiZ206+/7j
oDzF9qGHhFuxEGuGL1ufGm0LvCiOgNJpV4KGatjdminomS0PwI6v9Gz3r4mYBxeR
3xXV3xPpr3yEDu+ivQN8oMei4ttg++nuyk26gjAdEvz5uSQBV5lWvgjR6tlSPb/j
ca4d5AymuWmT110qcQ+ui7eoOWAfQIWj5Tlqk6YyXUnoMlD4c2c9/hOJZR51VF1n
diDONkHplqjzGfdHxDxCBXsIW1wi8h6PVHH54rDmYay1ojRVPJ7b2RBu8vgvWYoR
du6cY8Bp/1skQUEvtOBhFN62vAZ12s91jFO+plzyA9oDfAXVguW0yRWj5GNJmtPZ
YIcynpjvAgMBAAECggEAM1tzq3n5/Zk0jyRHvum5aKFi+HzH+t/nNVBPpjJxDLXF
dLTJSBIu0bY9uUkeDKtT7QbIMPgokEvdAyfka6PhYlRecsadHQObmDHMEKOFrRu4
CDXzSo2uBfMZSxTTV0VRRHxNKQT/QGN1kPdnsLJ1xXtwaqBIYnLTN2FrqvRKer4+
molQK8v838q4tOVT0ZKjIFHX+zyebKqJDOsCO+jDnrKIw3VTID6iZ0DNwGp5xNJT
HOLiT7CQM7Lg7fMdQp9xQxrGWJFw26cuvewmBuPdZRdqc8indgT5pq+VIZkLeTAB
XMnf0W35abwIUlfMIvhVZOiaeZzMwzNuV/Qp9GPFAQKBgQDzVpWxefxmTyHCWXz4
2m+wojoxEBgn6GRdvUbji7vyRRlmGZRNVybwqqxXyCq3RYdIwV55Ihk7O0mmO4g+
ESuDchZaMxouzXzDKz8jyoG6Gxymd8QJetnA8ctFwUGnTrK3sCxnTAjphGSf1PlT
vdev+f6T/QT0MT7qxUloDC9hAQKBgQC8LCF6OaFlZdi/kKPFcXbYIxFWRCdiun/A
xAL3+UsMllF53aZcWnIsm9rv0sKhPfrTGCp0eLUyinZzjxzdGDeb5hqafWVArXfT
xj4NC5uQVO2w0HGqB0BpZkBCtiyu25Pw0xPvXlcvwCBYcIDvJuf0hwiGBBKPk1b8
LjlkbfoJ7wKBgQDZsU499gmtZYGoIvLAlnpxJNC2b9WMbkTL77bpfmrntJWiV6Pr
BNrbV3TTG0nLp7H9jrB74dt8t++NfZjHHgk1kO0aSLlVwZOp7piP5mzkF7kr291P
Nc505FubzeZ0TN1po3w19TnL3xs+OgPLvPymfBoaPrMd2qiU02Z2ZOBGAQKBgA9V
GTU4VOpKLisNwgpogGKEGPmKfBsTTy2JyyQhb/gKl4Dyioej5wGzgVdhOPKidjmV
EoCDBWCk35ny40swmfdd/HTyGrn2aHkdAhlWBMrx4Jwzn89W3+y2pC3LYkCtK5TH
3iv25+vAH+KU6CyUYvoNtqgU1N5WBxRtP8frHiCJAoGAOtq0v8K2KKrEI6L/jHhJ
eNZVUWdGQTZx33vvt5lIboxrNyajaBo25LpmmEAKOhgD61ivHJNrxVDkOMKNx2xt
WnLZvQr7M62SdjWFmcFuY/xRbaX6IOW6C9qps3MdtJeFVw9P6z7udXfXHsCEbQvd
YyR0bVDYLxbCAdYjs7PyCA8=
-----END PRIVATE KEY-----
Loading