Skip to content

Commit 060cbfe

Browse files
authored
Finished GNP Auth and SimSwap MVP implementation (#475)
* Finished GNP Auth and SimSwap MVP implementation * Add documentation * PSR-4 fix * clean imports * Finished GNP Auth and SimSwap MVP implementation * bring psr7 trait back (#482) * Fix broken git history
1 parent 9d6b64a commit 060cbfe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+692
-85
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,52 @@ $response = $client->account()->updateConfig([
980980
print_r($response->toArray());
981981
```
982982

983+
### Use SimSwap to Check the Status and Date of a SIM in a handset
984+
985+
In order to use Vonage's Network APIs you'll need to be enabled within
986+
the [Vonage Network Registry](https://developer.vonage.com/en/getting-started-network/registration?source=sim-swap)
987+
988+
Once you have a registered MSNDIN, you will be able to use SimSwap.
989+
990+
SimSwap uses the Global Network Platform authentication mechanism, so the
991+
authorisation flow looks a little different from other API Clients. Under
992+
the hood, the SDK will handle multiple calls for you to configure a
993+
CAMARA standard access token.
994+
995+
Here's an example of checking if a SIM has been recently been swapped:
996+
997+
```php
998+
$credentials = new \Vonage\Client\Credentials\Gnp(
999+
'tel:+447700900000',
1000+
fopen('./my-private-key'),
1001+
'my-application-id'
1002+
);
1003+
1004+
$client = new \Vonage\Client($credentials);
1005+
1006+
if ($client->simswap()->checkSimSwap('07700009999', 240)) {
1007+
echo 'Warning: SIM Swap Check Failed'
1008+
} else {
1009+
echo 'SIM Swap Check Pass'
1010+
}
1011+
```
1012+
1013+
And here is how you retrieve the swap date:
1014+
1015+
```php
1016+
$credentials = new \Vonage\Client\Credentials\Gnp(
1017+
'tel:+447700900000',
1018+
fopen('./my-private-key'),
1019+
'my-application-id'
1020+
);
1021+
1022+
$client = new \Vonage\Client($credentials);
1023+
$date = $client->simswap()->checkSimSwapDate('07700009999')
1024+
1025+
echo $date;
1026+
```
1027+
1028+
9831029
### Get Information About a Number
9841030

9851031
The [Number Insights API](https://developer.nexmo.com/api/number-insight) allows a user to check that a number is valid and to find out more about how to use it.

phpunit.xml.dist

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@
2323
<testsuite name="verify2">
2424
<directory>test/Verify2</directory>
2525
</testsuite>
26+
<testsuite name="client">
27+
<directory>test/Client</directory>
28+
</testsuite>
2629
<testsuite name="conversations">
2730
<directory>test/Conversation</directory>
2831
</testsuite>
32+
<testsuite name="client">
33+
<directory>test/Client</directory>
34+
</testsuite>
2935
<testsuite name="proactive_connect">
3036
<directory>test/ProactiveConnect</directory>
3137
</testsuite>
@@ -41,6 +47,9 @@
4147
<testsuite name="sms">
4248
<directory>test/SMS</directory>
4349
</testsuite>
50+
<testsuite name="simswap">
51+
<directory>test/SimSwap</directory>
52+
</testsuite>
4453
<testsuite name="subaccount">
4554
<directory>test/Subaccount</directory>
4655
</testsuite>

src/Account/ClientFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function __invoke(ContainerInterface $container): Client
1717
->setBaseUrl($accountApi->getClient()->getRestUrl())
1818
->setIsHAL(false)
1919
->setBaseUri('/account')
20-
->setAuthHandler(new BasicQueryHandler())
20+
->setAuthHandlers(new BasicQueryHandler())
2121
;
2222

2323
return new Client($accountApi);

src/Application/ClientFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function __invoke(ContainerInterface $container): Client
1717
$api
1818
->setBaseUri('/v2/applications')
1919
->setCollectionName('applications')
20-
->setAuthHandler(new BasicHandler());
20+
->setAuthHandlers(new BasicHandler());
2121

2222
return new Client($api, new Hydrator());
2323
}

src/Client.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
use Vonage\Numbers\ClientFactory as NumbersClientFactory;
4444
use Vonage\Redact\ClientFactory as RedactClientFactory;
4545
use Vonage\Secrets\ClientFactory as SecretsClientFactory;
46+
use Vonage\SimSwap\ClientFactory as SimSwapClientFactory;
4647
use Vonage\SMS\ClientFactory as SMSClientFactory;
4748
use Vonage\Subaccount\ClientFactory as SubaccountClientFactory;
4849
use Vonage\Messages\ClientFactory as MessagesClientFactory;
@@ -79,6 +80,7 @@
7980
* @method Numbers\Client numbers()
8081
* @method Redact\Client redact()
8182
* @method Secrets\Client secrets()
83+
* @method SimSwap\Client simswap()
8284
* @method SMS\Client sms()
8385
* @method Subaccount\Client subaccount()
8486
* @method Users\Client users()
@@ -219,6 +221,7 @@ public function __construct(
219221
'messages' => MessagesClientFactory::class,
220222
'redact' => RedactClientFactory::class,
221223
'secrets' => SecretsClientFactory::class,
224+
'simswap' => SimSwapClientFactory::class,
222225
'sms' => SMSClientFactory::class,
223226
'subaccount' => SubaccountClientFactory::class,
224227
'users' => UsersClientFactory::class,
@@ -228,6 +231,7 @@ public function __construct(
228231

229232
// Additional utility classes
230233
APIResource::class => APIResource::class,
234+
Client::class => function() { return $this; }
231235
];
232236

233237
if (class_exists('Vonage\Video\ClientFactory')) {

src/Client/APIResource.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
declare(strict_types=1);
34

45
namespace Vonage\Client;
@@ -28,7 +29,7 @@ class APIResource implements ClientAwareInterface
2829
/**
2930
* @var HandlerInterface[]
3031
*/
31-
protected array $authHandler = [];
32+
protected array $authHandlers = [];
3233

3334
/**
3435
* Base URL that we will hit. This can be overridden from the underlying
@@ -68,8 +69,8 @@ public function addAuth(RequestInterface $request): RequestInterface
6869
{
6970
$credentials = $this->getClient()->getCredentials();
7071

71-
if (is_array($this->getAuthHandler())) {
72-
foreach ($this->getAuthHandler() as $handler) {
72+
if (is_array($this->getAuthHandlers())) {
73+
foreach ($this->getAuthHandlers() as $handler) {
7374
try {
7475
$request = $handler($request, $credentials);
7576
break;
@@ -84,7 +85,7 @@ public function addAuth(RequestInterface $request): RequestInterface
8485
return $request;
8586
}
8687

87-
return $this->getAuthHandler()($request, $credentials);
88+
return $this->getAuthHandlers()($request, $credentials);
8889
}
8990

9091
/**
@@ -106,7 +107,7 @@ public function create(array $body, string $uri = '', array $headers = []): ?arr
106107

107108
$request->getBody()->write(json_encode($body));
108109

109-
if ($this->getAuthHandler()) {
110+
if ($this->getAuthHandlers()) {
110111
$request = $this->addAuth($request);
111112
}
112113

@@ -154,7 +155,7 @@ public function delete(string $id, array $headers = []): ?array
154155
$headers
155156
);
156157

157-
if ($this->getAuthHandler()) {
158+
if ($this->getAuthHandlers()) {
158159
$request = $this->addAuth($request);
159160
}
160161

@@ -207,7 +208,7 @@ public function get($id, array $query = [], array $headers = [], bool $jsonRespo
207208
$headers
208209
);
209210

210-
if ($this->getAuthHandler()) {
211+
if ($this->getAuthHandlers()) {
211212
$request = $this->addAuth($request);
212213
}
213214

@@ -231,10 +232,10 @@ public function get($id, array $query = [], array $headers = [], bool $jsonRespo
231232
return json_decode($response->getBody()->getContents(), true);
232233
}
233234

234-
public function getAuthHandler()
235+
public function getAuthHandlers()
235236
{
236237
// If we have not set a handler, default to Basic and issue warning.
237-
if (!$this->authHandler) {
238+
if (!$this->authHandlers) {
238239
$this->log(
239240
LogLevel::WARNING,
240241
'Warning: no authorisation handler set for this Client. Defaulting to Basic which might not be
@@ -244,7 +245,7 @@ public function getAuthHandler()
244245
return new BasicHandler();
245246
}
246247

247-
return $this->authHandler;
248+
return $this->authHandlers;
248249
}
249250

250251
public function getBaseUrl(): ?string
@@ -352,12 +353,12 @@ public function search(?FilterInterface $filter = null, string $uri = ''): Itera
352353
*
353354
* @return $this
354355
*/
355-
public function setAuthHandler($handler): self
356+
public function setAuthHandlers($handler): self
356357
{
357358
if (!is_array($handler)) {
358359
$handler = [$handler];
359360
}
360-
$this->authHandler = $handler;
361+
$this->authHandlers = $handler;
361362

362363
return $this;
363364
}
@@ -430,7 +431,7 @@ public function submit(array $formData = [], string $uri = '', array $headers =
430431
$headers
431432
);
432433

433-
if ($this->getAuthHandler()) {
434+
if ($this->getAuthHandlers()) {
434435
$request = $this->addAuth($request);
435436
}
436437

@@ -473,7 +474,7 @@ protected function updateEntity(string $method, string $id, array $body, array $
473474
$headers
474475
);
475476

476-
if ($this->getAuthHandler()) {
477+
if ($this->getAuthHandlers()) {
477478
$request = $this->addAuth($request);
478479
}
479480

src/Client/Credentials/Gnp.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Vonage\Client\Credentials;
6+
7+
class Gnp extends Keypair
8+
{
9+
public function __construct(protected string $msisdn, protected string $key, $application = null)
10+
{
11+
parent::__construct($key, $application);
12+
}
13+
14+
public function getMsisdn(): string
15+
{
16+
return $this->msisdn;
17+
}
18+
19+
public function setMsisdn(string $msisdn): Gnp
20+
{
21+
$this->msisdn = $msisdn;
22+
23+
return $this;
24+
}
25+
}

src/Client/Credentials/Handler/AbstractHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
abstract class AbstractHandler implements HandlerInterface
1010
{
11-
abstract function __invoke(RequestInterface $request, CredentialsInterface $credentials): RequestInterface;
11+
abstract public function __invoke(RequestInterface $request, CredentialsInterface $credentials): RequestInterface;
1212

1313
protected function extract(string $class, CredentialsInterface $credentials): CredentialsInterface
1414
{
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Vonage\Client\Credentials\Handler;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
use Vonage\Client;
7+
use Vonage\Client\APIResource;
8+
use Vonage\Client\Credentials\CredentialsInterface;
9+
use Vonage\Client\Credentials\Gnp;
10+
11+
/**
12+
* This handler is for Vonage GNP APIs that require the CAMARA standard OAuth Flow
13+
*/
14+
class GnpHandler extends AbstractHandler
15+
{
16+
use Client\ClientAwareTrait;
17+
use Client\ScopeAwareTrait;
18+
19+
protected const VONAGE_GNP_AUTH_BACKEND_URL = 'https://api-eu.vonage.com/oauth2/bc-authorize';
20+
protected const VONAGE_GNP_AUTH_TOKEN_URL = 'https://api-eu.vonage.com/oauth2/token';
21+
protected const VONAGE_GNP_AUTH_TOKEN_GRANT_TYPE = 'urn:openid:params:grant-type:ciba';
22+
23+
public function __invoke(RequestInterface $request, CredentialsInterface $credentials): RequestInterface
24+
{
25+
/** @var Gnp $credentials */
26+
$credentials = $this->extract(Gnp::class, $credentials);
27+
$msisdn = $credentials->getMsisdn();
28+
29+
// Request OIDC, returns Auth Request ID
30+
// Reconfigure new client for GNP Auth
31+
$api = new APIResource();
32+
$api->setAuthHandlers(new KeypairHandler());
33+
$api->setClient($this->getClient());
34+
$api->setBaseUrl(self::VONAGE_GNP_AUTH_BACKEND_URL);
35+
36+
// This handler requires an injected client configured with a Gnp credentials object and a configured scope
37+
$response = $api->submit([
38+
'login_hint' => $msisdn,
39+
'scope' => $this->getScope()
40+
]);
41+
42+
$decoded = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
43+
44+
$authReqId = $decoded['auth_req_id'];
45+
46+
// CAMARA Access Token
47+
$api->setBaseUrl(self::VONAGE_GNP_AUTH_TOKEN_URL);
48+
$response = $api->submit([
49+
'grant_type' => self::VONAGE_GNP_AUTH_TOKEN_GRANT_TYPE,
50+
'auth_req_id' => $authReqId
51+
]);
52+
53+
$decoded = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
54+
55+
$token = $decoded['access_token'];
56+
57+
// Add CAMARA Access Token to request and return to make API call
58+
return $request->withHeader('Authorization', 'Bearer ' . $token);
59+
}
60+
}

src/Client/ScopeAwareTrait.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Vonage\Client;
6+
7+
use RuntimeException;
8+
use Vonage\Client;
9+
10+
trait ScopeAwareTrait
11+
{
12+
protected ?string $scope = null;
13+
14+
public function setScope(string $scope): self
15+
{
16+
$this->scope = $scope;
17+
18+
return $this;
19+
}
20+
21+
public function getScope(): ?string
22+
{
23+
return $this->scope;
24+
}
25+
}

0 commit comments

Comments
 (0)