Skip to content

Commit 3680db7

Browse files
SecondeJKyosatakyosh
authored
Feature/gnp number verification (#483)
* Finished GNP Auth and SimSwap MVP implementation * Add documentation * init * Add next workflow to SDK (#476) * Add from validation on request (#477) * Feature/conversations (#478) * WIP * WIP * WIP * WIP * WIP * refactor to remove attempt at putting all event objects in * Added docs * phpstan fix number object * Update README.md 8.1 is minimum * Remove Orphaned Code (#480) * Big cleanup of unused internals * clean some unused imports * Fix baseline, mark some deprecations coming * fix ConversationClientFactory (#479) Co-authored-by: yosh <yosh@latvm.unnamed.typo.pw> * Remove a load of traits not being used by anything (#481) * bring psr7 trait back (#482) * 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 * Bulk of work pre-testing * Testing complete * Configure scopes correctly within the client, remove rogue die statement --------- Co-authored-by: Yoshitaka Takeuchi <yosh@baud.jp> Co-authored-by: yosh <yosh@latvm.unnamed.typo.pw>
1 parent 060cbfe commit 3680db7

File tree

21 files changed

+706
-88
lines changed

21 files changed

+706
-88
lines changed

phpunit.xml.dist

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
<testsuite name="client">
2727
<directory>test/Client</directory>
2828
</testsuite>
29+
<testsuite name="sim_swap">
30+
<directory>test/SimSwap</directory>
31+
</testsuite>
32+
<testsuite name="number_verification">
33+
<directory>test/NumberVerification</directory>
34+
</testsuite>
2935
<testsuite name="conversations">
3036
<directory>test/Conversation</directory>
3137
</testsuite>

src/Client.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Vonage\Client\Credentials\Basic;
2525
use Vonage\Client\Credentials\Container;
2626
use Vonage\Client\Credentials\CredentialsInterface;
27+
use Vonage\Client\Credentials\Gnp;
2728
use Vonage\Client\Credentials\Handler\BasicHandler;
2829
use Vonage\Client\Credentials\Handler\SignatureBodyFormHandler;
2930
use Vonage\Client\Credentials\Handler\SignatureBodyHandler;
@@ -171,12 +172,12 @@ public function __construct(
171172

172173
$this->setHttpClient($client);
173174

174-
// Make sure we know how to use the credentials
175175
if (
176176
!($credentials instanceof Container) &&
177177
!($credentials instanceof Basic) &&
178178
!($credentials instanceof SignatureSecret) &&
179-
!($credentials instanceof Keypair)
179+
!($credentials instanceof Keypair) &&
180+
!($credentials instanceof Gnp)
180181
) {
181182
throw new RuntimeException('unknown credentials type: ' . $credentials::class);
182183
}
@@ -231,13 +232,15 @@ public function __construct(
231232

232233
// Additional utility classes
233234
APIResource::class => APIResource::class,
234-
Client::class => function() { return $this; }
235+
Client::class => function () {
236+
return $this;
237+
}
235238
];
236239

237240
if (class_exists('Vonage\Video\ClientFactory')) {
238241
$services['video'] = 'Vonage\Video\ClientFactory';
239242
} else {
240-
$services['video'] = function() {
243+
$services['video'] = function () {
241244
throw new \RuntimeException('Please install @vonage/video to use the Video API');
242245
};
243246
}
@@ -360,7 +363,6 @@ public static function authRequest(RequestInterface $request, Basic $credentials
360363

361364
/**
362365
* @throws ClientException
363-
* @deprecated Use the Vonage/JWT library if you need to generate a token
364366
*/
365367
public function generateJwt($claims = []): Token
366368
{

src/Client/Credentials/Container.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class Container extends AbstractCredentials
1414
protected array $types = [
1515
Basic::class,
1616
SignatureSecret::class,
17-
Keypair::class
17+
Keypair::class,
18+
Gnp::class
1819
];
1920

2021
/**

src/Client/Credentials/Gnp.php

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,26 @@
44

55
namespace Vonage\Client\Credentials;
66

7-
class Gnp extends Keypair
7+
use Lcobucci\JWT\Encoding\JoseEncoder;
8+
use Lcobucci\JWT\Token;
9+
use Vonage\JWT\TokenGenerator;
10+
11+
class Gnp extends AbstractCredentials
812
{
9-
public function __construct(protected string $msisdn, protected string $key, $application = null)
13+
protected ?string $code = null;
14+
protected ?string $state = null;
15+
protected ?string $redirectUri = null;
16+
17+
public function __construct(
18+
protected string $msisdn,
19+
protected string $key,
20+
protected $application = null
21+
) {
22+
}
23+
24+
public function getKeyRaw(): string
1025
{
11-
parent::__construct($key, $application);
26+
return $this->key;
1227
}
1328

1429
public function getMsisdn(): string
@@ -22,4 +37,95 @@ public function setMsisdn(string $msisdn): Gnp
2237

2338
return $this;
2439
}
40+
41+
public function getCode(): ?string
42+
{
43+
return $this->code;
44+
}
45+
46+
public function setCode(?string $code): Gnp
47+
{
48+
$this->code = $code;
49+
return $this;
50+
}
51+
52+
public function getState(): ?string
53+
{
54+
return $this->state;
55+
}
56+
57+
public function setState(?string $state): Gnp
58+
{
59+
$this->state = $state;
60+
return $this;
61+
}
62+
63+
public function getRedirectUri(): ?string
64+
{
65+
return $this->redirectUri;
66+
}
67+
68+
public function setRedirectUri(?string $redirectUri): Gnp
69+
{
70+
$this->redirectUri = $redirectUri;
71+
return $this;
72+
}
73+
74+
public function generateJwt(array $claims = []): Token
75+
{
76+
$generator = new TokenGenerator($this->application, $this->getKeyRaw());
77+
78+
if (isset($claims['exp'])) {
79+
// This will change to an Exception in 5.0
80+
trigger_error('Expiry date is automatically generated from now and TTL, so cannot be passed in
81+
as an argument in claims', E_USER_WARNING);
82+
unset($claims['nbf']);
83+
}
84+
85+
if (isset($claims['ttl'])) {
86+
$generator->setTTL($claims['ttl']);
87+
unset($claims['ttl']);
88+
}
89+
90+
if (isset($claims['jti'])) {
91+
$generator->setJTI($claims['jti']);
92+
unset($claims['jti']);
93+
}
94+
95+
if (isset($claims['nbf'])) {
96+
// Due to older versions of lcobucci/jwt, this claim has
97+
// historic fraction conversation issues. For now, nbf is not supported.
98+
// This will change to an Exception in 5.0
99+
trigger_error('NotBefore Claim is not supported in Vonage JWT', E_USER_WARNING);
100+
unset($claims['nbf']);
101+
}
102+
103+
if (isset($claims['sub'])) {
104+
$generator->setSubject($claims['sub']);
105+
unset($claims['sub']);
106+
}
107+
108+
if (!empty($claims)) {
109+
foreach ($claims as $claim => $value) {
110+
$generator->addClaim($claim, $value);
111+
}
112+
}
113+
114+
$jwt = $generator->generate();
115+
$parser = new Token\Parser(new JoseEncoder());
116+
117+
// Backwards compatible for signature. In 5.0 this will return a string value
118+
return $parser->parse($jwt);
119+
}
120+
121+
public function getApplication(): ?string
122+
{
123+
return $this->application;
124+
}
125+
126+
public function setApplication(mixed $application): Keypair
127+
{
128+
$this->application = $application;
129+
return $this;
130+
}
25131
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Vonage\Client\Credentials\Handler;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
use Vonage\Client\Credentials\CredentialsInterface;
7+
use Vonage\Client\Credentials\Gnp;
8+
use Vonage\Client\Credentials\Keypair;
9+
10+
class GnpKeypairHandler extends AbstractHandler
11+
{
12+
public function __invoke(RequestInterface $request, CredentialsInterface $credentials): RequestInterface
13+
{
14+
/** @var Keypair $credentials */
15+
$credentials = $this->extract(Gnp::class, $credentials);
16+
$token = $credentials->generateJwt();
17+
18+
return $request->withHeader('Authorization', 'Bearer ' . $token->toString());
19+
}
20+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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 NumberVerificationGnpHandler extends SimSwapGnpHandler
15+
{
16+
use Client\ClientAwareTrait;
17+
use Client\ScopeAwareTrait;
18+
19+
protected ?string $baseUrl = null;
20+
protected ?string $tokenUrl = null;
21+
22+
public function getBaseUrl(): ?string
23+
{
24+
return $this->baseUrl;
25+
}
26+
27+
public function setBaseUrl(?string $baseUrl): NumberVerificationGnpHandler
28+
{
29+
$this->baseUrl = $baseUrl;
30+
return $this;
31+
}
32+
33+
public function getTokenUrl(): ?string
34+
{
35+
return $this->tokenUrl;
36+
}
37+
38+
public function setTokenUrl(?string $tokenUrl): NumberVerificationGnpHandler
39+
{
40+
$this->tokenUrl = $tokenUrl;
41+
return $this;
42+
}
43+
44+
public function __invoke(RequestInterface $request, CredentialsInterface $credentials): RequestInterface
45+
{
46+
/** @var Gnp $credentials */
47+
$credentials = $this->extract(Gnp::class, $credentials);
48+
49+
// submit the code to CAMARA endpoint
50+
$api = new APIResource();
51+
$api->setAuthHandlers(new GnpKeypairHandler());
52+
$api->setClient($this->getClient());
53+
$api->setBaseUrl('https://api-eu.vonage.com/oauth2/token');
54+
55+
$tokenResponse = $api->submit([
56+
'grant_type' => 'authorization_code',
57+
'code' => $credentials->getCode(),
58+
'redirect_uri' => $credentials->getRedirectUri()
59+
]);
60+
61+
$payload = json_decode($tokenResponse, true);
62+
63+
// Add CAMARA Access Token to request and return to make API call
64+
return $request->withHeader('Authorization', 'Bearer ' . $payload['access_token']);
65+
}
66+
}

src/Client/Credentials/Handler/GnpHandler.php renamed to src/Client/Credentials/Handler/SimSwapGnpHandler.php

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,49 @@
1111
/**
1212
* This handler is for Vonage GNP APIs that require the CAMARA standard OAuth Flow
1313
*/
14-
class GnpHandler extends AbstractHandler
14+
class SimSwapGnpHandler extends AbstractHandler
1515
{
1616
use Client\ClientAwareTrait;
1717
use Client\ScopeAwareTrait;
1818

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';
19+
protected ?string $baseUrl = null;
20+
protected ?string $tokenUrl = null;
21+
22+
public function getBaseUrl(): ?string
23+
{
24+
return $this->baseUrl;
25+
}
26+
27+
public function setBaseUrl(?string $baseUrl): SimSwapGnpHandler
28+
{
29+
$this->baseUrl = $baseUrl;
30+
return $this;
31+
}
32+
33+
public function getTokenUrl(): ?string
34+
{
35+
return $this->tokenUrl;
36+
}
37+
38+
public function setTokenUrl(?string $tokenUrl): SimSwapGnpHandler
39+
{
40+
$this->tokenUrl = $tokenUrl;
41+
return $this;
42+
}
43+
44+
public string $token;
2245

2346
public function __invoke(RequestInterface $request, CredentialsInterface $credentials): RequestInterface
2447
{
2548
/** @var Gnp $credentials */
2649
$credentials = $this->extract(Gnp::class, $credentials);
2750
$msisdn = $credentials->getMsisdn();
2851

29-
// Request OIDC, returns Auth Request ID
30-
// Reconfigure new client for GNP Auth
3152
$api = new APIResource();
32-
$api->setAuthHandlers(new KeypairHandler());
53+
$api->setAuthHandlers(new GnpKeypairHandler());
3354
$api->setClient($this->getClient());
34-
$api->setBaseUrl(self::VONAGE_GNP_AUTH_BACKEND_URL);
55+
$api->setBaseUrl($this->getBaseUrl());
3556

36-
// This handler requires an injected client configured with a Gnp credentials object and a configured scope
3757
$response = $api->submit([
3858
'login_hint' => $msisdn,
3959
'scope' => $this->getScope()
@@ -44,9 +64,9 @@ public function __invoke(RequestInterface $request, CredentialsInterface $creden
4464
$authReqId = $decoded['auth_req_id'];
4565

4666
// CAMARA Access Token
47-
$api->setBaseUrl(self::VONAGE_GNP_AUTH_TOKEN_URL);
67+
$api->setBaseUrl($this->getTokenUrl());
4868
$response = $api->submit([
49-
'grant_type' => self::VONAGE_GNP_AUTH_TOKEN_GRANT_TYPE,
69+
'grant_type' => 'urn:openid:params:grant-type:ciba',
5070
'auth_req_id' => $authReqId
5171
]);
5272

src/Client/Credentials/Keypair.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
class Keypair extends AbstractCredentials
1818
{
19-
public function __construct(protected string $key, $application = null)
19+
public function __construct(protected string $key, protected ?string $application = null)
2020
{
2121
$this->credentials['key'] = $key;
2222

@@ -42,6 +42,17 @@ public function getKeyRaw(): string
4242
return $this->key;
4343
}
4444

45+
public function getApplication(): ?string
46+
{
47+
return $this->application;
48+
}
49+
50+
public function setApplication(mixed $application): Keypair
51+
{
52+
$this->application = $application;
53+
return $this;
54+
}
55+
4556
public function generateJwt(array $claims = []): Token
4657
{
4758
$generator = new TokenGenerator($this->application, $this->getKeyRaw());

0 commit comments

Comments
 (0)