Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
Add support for private key authentication
Browse files Browse the repository at this point in the history
This supports the following scenarios:

* Private key only
* Private key + API secret
* Private key + signature secret
  • Loading branch information
mheap committed Feb 8, 2018
1 parent eaa6189 commit d5866ae
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 20 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ NEXMO_KEY=my_api_key
NEXMO_SECRET=my_secret
```

Optionally, you could also set an `application_id` and `private_key` if required:

```dotenv
NEXMO_APPLICATION_ID=my_application_id
NEXMO_PRIVATE_KEY=./private.key
```

Usage
-----

Expand All @@ -139,6 +146,23 @@ $nexmo->message()->send([
]);
```

If you're using private key authentication, try making a voice call:

```php
Nexmo::calls()->create([
'to' => [[
'type' => 'phone',
'number' => '14155550100'
]],
'from' => [
'type' => 'phone',
'number' => '14155550101'
],
'answer_url' => ['https://example.com/webhook/answer'],
'event_url' => ['https://example.com/webhook/event']
]);
```

For more information on using the Nexmo client library, see the [official client library repository][client-library].

[client-library]: https://github.com/Nexmo/nexmo-php
14 changes: 14 additions & 0 deletions config/nexmo.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,18 @@

'signature_secret' => function_exists('env') ? env('NEXMO_SIGNATURE_SECRET', '') : '',

/*
|--------------------------------------------------------------------------
| Private Key
|--------------------------------------------------------------------------
|
| Private keys are used to generate JWTs for authentication. Generation is
| handled by the library. JWTs are required for newer APIs, such as voice
| and media
|
*/

'private_key' => function_exists('env') ? env('NEXMO_PRIVATE_KEY', '') : '',
'application_id' => function_exists('env') ? env('NEXMO_APPLICATION_ID', '') : '',

];
102 changes: 84 additions & 18 deletions src/NexmoServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,32 +77,67 @@ protected function createNexmoClient(Config $config)
$this->raiseRunTimeException('Missing nexmo configuration section.');
}

// Check for API_KEY.
if ($this->nexmoConfigHasNo('api_key')) {
$this->raiseRunTimeException('Missing nexmo configuration: "api_key".');
// Get Client Options.
$options = array_diff_key($config->get('nexmo'), ['private_key', 'application_id', 'api_key', 'api_secret', 'shared_secret']);

// Do we have a private key?
$privateKeyCredentials = null;
if ($this->nexmoConfigHas('private_key')) {
if ($this->nexmoConfigHasNo('application_id')) {
$this->raiseRunTimeException('You must provide nexmo.application_id when using a private key');
}

$privateKeyCredentials = $this->createPrivateKeyCredentials($config->get('nexmo.private_key'), $config->get('nexmo.application_id'));
}

// Neither type of Credentials could be resolved from config.
if ($this->nexmoConfigHasNo('api_secret') && $this->nexmoConfigHasNo('signature_secret')) {
$this->raiseRunTimeException('Missing nexmo configuration: "api_secret" or "signature_secret".');
$basicCredentials = null;
if ($this->nexmoConfigHas('api_secret')) {
$basicCredentials = $this->createBasicCredentials($config->get('nexmo.api_key'), $config->get('nexmo.api_secret'));
}

// Get Client Options.
$options = array_diff_key($config->get('nexmo'), ['api_key', 'api_secret', 'shared_secret']);
$signatureCredentials = null;
if ($this->nexmoConfigHas('signature_secret')) {
$signatureCredentials = $this->createSignatureCredentials($config->get('nexmo.api_key'), $config->get('nexmo.signature_secret'));
}

// Check whether config is setup for using API_SECRET
// otherwise use SIGNATURE.
if ($this->nexmoConfigHas('api_secret')) {
return new Client(
$this->createBasicCredentials($config->get('nexmo.api_key'), $config->get('nexmo.api_secret')),
$options
// We can have basic only, signature only, private key only or
// we can have private key + basic/signature, so let's work out
// what's been provided
if ($basicCredentials && $signatureCredentials) {
$this->raiseRunTimeException('Provide either nexmo.api_secret or nexmo.signature_secret');
}

if ($privateKeyCredentials && $basicCredentials) {
$credentials = new Client\Credentials\Container(
$privateKeyCredentials,
$basicCredentials
);
} else if ($privateKeyCredentials && $signatureCredentials) {
$credentials = new Client\Credentials\Container(
$privateKeyCredentials,
$signatureCredentials
);
} else if ($privateKeyCredentials) {
$credentials = $privateKeyCredentials;
} else if ($signatureCredentials) {
$credentials = $signatureCredentials;
} else if ($basicCredentials) {
$credentials = $basicCredentials;
} else {
$possibleNexmoKeys = [
'api_key + api_secret',
'api_key + signature_secret',
'private_key + application_id',
'api_key + api_secret + private_key + application_id',
'api_key + signature_secret + private_key + application_id',
];
$this->raiseRunTimeException(
'Please provide Nexmo API credentials. Possible combinations: '
. join(", ", $possibleNexmoKeys)
);
}

return new Client(
$this->createSignatureCredentials($config->get('nexmo.api_key'), $config->get('nexmo.signature_secret')),
$options
);
return new Client($credentials, $options);
}

/**
Expand Down Expand Up @@ -179,6 +214,37 @@ protected function createSignatureCredentials($key, $signatureSecret)
return new Client\Credentials\SignatureSecret($key, $signatureSecret);
}

/**
* Create Keypair credentials for client.
*
* @param string $key
* @param string $applicationId
*
* @return Client\Credentials\Keypair
*/
protected function createPrivateKeyCredentials($key, $applicationId)
{
return new Client\Credentials\Keypair($this->loadPrivateKey($key), $applicationId);
}

/**
* Load private key contents from root directory
*/
protected function loadPrivateKey($key)
{
if (app()->runningUnitTests()) {
return '===FAKE-KEY===';
}

// If it's a relative path, start searching in the
// project root
if ($key[0] !== '/') {
$key = base_path().'/'.$key;
}

return file_get_contents($key);
}

/**
* Raises Runtime exception.
*
Expand Down
43 changes: 43 additions & 0 deletions tests/TestClientPrivateKeyBasicCredentials.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Nexmo\Laravel\Tests;

use Nexmo\Client;

class TestClientPrivateKeyBasicCredentials extends AbstractTestCase
{
/**
* Define environment setup.
*
* @param \Illuminate\Foundation\Application $app
*
* @return void
*/
protected function getEnvironmentSetUp($app)
{
$app['config']->set('nexmo.private_key', '/path/to/key');
$app['config']->set('nexmo.application_id', 'application-id-123');
$app['config']->set('nexmo.api_key', 'my_api_key');
$app['config']->set('nexmo.api_secret', 'my_secret');
}

/**
* Test that our Nexmo client is created with
* a container with key + basic credentials.
*
* @return void
*/
public function testClientCreatedWithPrivateKeyBasicCredentials()
{
$client = app(Client::class);
$credentialsObject = $this->getClassProperty(Client::class, 'credentials', $client);

$credentialsArray = $this->getClassProperty(Client\Credentials\Container::class, 'credentials', $credentialsObject);
$keypairCredentials = $this->getClassProperty(Client\Credentials\Keypair::class, 'credentials', $credentialsArray[Client\Credentials\Keypair::class]);
$basicCredentials = $this->getClassProperty(Client\Credentials\Basic::class, 'credentials', $credentialsArray[Client\Credentials\Basic::class]);

$this->assertInstanceOf(Client\Credentials\Container::class, $credentialsObject);
$this->assertEquals(['key' => '===FAKE-KEY===', 'application' => 'application-id-123'], $keypairCredentials);
$this->assertEquals(['api_key' => 'my_api_key', 'api_secret' => 'my_secret'], $basicCredentials);
}
}
37 changes: 37 additions & 0 deletions tests/TestClientPrivateKeyCredentials.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Nexmo\Laravel\Tests;

use Nexmo\Client;

class TestClientPrivateKeyCredentials extends AbstractTestCase
{
/**
* Define environment setup.
*
* @param \Illuminate\Foundation\Application $app
*
* @return void
*/
protected function getEnvironmentSetUp($app)
{
$app['config']->set('nexmo.private_key', '/path/to/key');
$app['config']->set('nexmo.application_id', 'application-id-123');
}

/**
* Test that our Nexmo client is created with
* the private key credentials
*
* @return void
*/
public function testClientCreatedWithPrivateKeyCredentials()
{
$client = app(Client::class);
$credentialsObject = $this->getClassProperty(Client::class, 'credentials', $client);
$credentialsArray = $this->getClassProperty(Client\Credentials\Keypair::class, 'credentials', $credentialsObject);

$this->assertInstanceOf(Client\Credentials\Keypair::class, $credentialsObject);
$this->assertEquals(['key' => '===FAKE-KEY===', 'application' => 'application-id-123'], $credentialsArray);
}
}
43 changes: 43 additions & 0 deletions tests/TestClientPrivateKeySignatureCredentials.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Nexmo\Laravel\Tests;

use Nexmo\Client;

class TestClientPrivateKeySignatureCredentials extends AbstractTestCase
{
/**
* Define environment setup.
*
* @param \Illuminate\Foundation\Application $app
*
* @return void
*/
protected function getEnvironmentSetUp($app)
{
$app['config']->set('nexmo.private_key', '/path/to/key');
$app['config']->set('nexmo.application_id', 'application-id-123');
$app['config']->set('nexmo.api_key', 'my_api_key');
$app['config']->set('nexmo.signature_secret', 'my_signature');
}

/**
* Test that our Nexmo client is created with
* a container with private key + signature credentials
*
* @return void
*/
public function testClientCreatedWithPrivateKeySignatureCredentials()
{
$client = app(Client::class);
$credentialsObject = $this->getClassProperty(Client::class, 'credentials', $client);

$credentialsArray = $this->getClassProperty(Client\Credentials\Container::class, 'credentials', $credentialsObject);
$keypairCredentials = $this->getClassProperty(Client\Credentials\Keypair::class, 'credentials', $credentialsArray[Client\Credentials\Keypair::class]);
$signatureCredentials = $this->getClassProperty(Client\Credentials\SignatureSecret::class, 'credentials', $credentialsArray[Client\Credentials\SignatureSecret::class]);

$this->assertInstanceOf(Client\Credentials\Container::class, $credentialsObject);
$this->assertEquals(['key' => '===FAKE-KEY===', 'application' => 'application-id-123'], $keypairCredentials);
$this->assertEquals(['api_key' => 'my_api_key', 'signature_secret' => 'my_signature'], $signatureCredentials);
}
}
2 changes: 1 addition & 1 deletion tests/TestClientSignatureAPICredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function getEnvironmentSetUp($app)

/**
* Test that our Nexmo client is created with
* the Basic API credentials.
* the signature credentials
*
* @return void
*/
Expand Down
2 changes: 1 addition & 1 deletion tests/TestNoNexmoConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protected function getEnvironmentSetUp($app)
* @return void
*
* @expectedException \RuntimeException
* @expectedExceptionMessage Missing nexmo configuration: "api_secret" or "signature_secret".
* @expectedExceptionMessage Please provide Nexmo API credentials. Possible combinations: api_key + api_secret, api_key + signature_secret, private_key + application_id, api_key + api_secret + private_key + application_id, api_key + signature_secret + private_key + application_id
*/
public function testWhenNoConfigurationIsGivenExceptionIsRaised()
{
Expand Down

0 comments on commit d5866ae

Please sign in to comment.