diff --git a/README.md b/README.md index 2c5c525..973ae22 100644 --- a/README.md +++ b/README.md @@ -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 ----- @@ -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 diff --git a/config/nexmo.php b/config/nexmo.php index e43bac1..cce171e 100644 --- a/config/nexmo.php +++ b/config/nexmo.php @@ -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', '') : '', + ]; diff --git a/src/NexmoServiceProvider.php b/src/NexmoServiceProvider.php index b8c6dee..5f5bd67 100644 --- a/src/NexmoServiceProvider.php +++ b/src/NexmoServiceProvider.php @@ -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); } /** @@ -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. * diff --git a/tests/TestClientPrivateKeyBasicCredentials.php b/tests/TestClientPrivateKeyBasicCredentials.php new file mode 100644 index 0000000..2b2e105 --- /dev/null +++ b/tests/TestClientPrivateKeyBasicCredentials.php @@ -0,0 +1,43 @@ +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); + } +} diff --git a/tests/TestClientPrivateKeyCredentials.php b/tests/TestClientPrivateKeyCredentials.php new file mode 100644 index 0000000..731b329 --- /dev/null +++ b/tests/TestClientPrivateKeyCredentials.php @@ -0,0 +1,37 @@ +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); + } +} diff --git a/tests/TestClientPrivateKeySignatureCredentials.php b/tests/TestClientPrivateKeySignatureCredentials.php new file mode 100644 index 0000000..d34cc7d --- /dev/null +++ b/tests/TestClientPrivateKeySignatureCredentials.php @@ -0,0 +1,43 @@ +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); + } +} diff --git a/tests/TestClientSignatureAPICredentials.php b/tests/TestClientSignatureAPICredentials.php index dc34693..e08ad64 100644 --- a/tests/TestClientSignatureAPICredentials.php +++ b/tests/TestClientSignatureAPICredentials.php @@ -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 */ diff --git a/tests/TestNoNexmoConfiguration.php b/tests/TestNoNexmoConfiguration.php index d351b63..76b5ada 100644 --- a/tests/TestNoNexmoConfiguration.php +++ b/tests/TestNoNexmoConfiguration.php @@ -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() {