Skip to content

Commit 05c3a89

Browse files
committed
[client] add JWT management methods
1 parent 7df6ff0 commit 05c3a89

6 files changed

Lines changed: 429 additions & 0 deletions

File tree

src/Client.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use AndroidSmsGateway\Domain\Webhook;
1010
use AndroidSmsGateway\Domain\MessagesExportRequest;
1111
use AndroidSmsGateway\Domain\Settings;
12+
use AndroidSmsGateway\Domain\TokenRequest;
13+
use AndroidSmsGateway\Domain\TokenResponse;
1214
use AndroidSmsGateway\Exceptions\HttpException;
1315
use Http\Discovery\Psr17FactoryDiscovery;
1416
use Http\Discovery\Psr18ClientDiscovery;
@@ -379,6 +381,42 @@ public function DeleteWebhook(string $id): void {
379381
);
380382
}
381383

384+
/**
385+
* Generate a new JWT token
386+
*
387+
* @param TokenRequest $request
388+
* @return TokenResponse
389+
*/
390+
public function GenerateToken(TokenRequest $request): TokenResponse {
391+
$path = '/auth/token';
392+
393+
$response = $this->sendRequest(
394+
'POST',
395+
$path,
396+
$request
397+
);
398+
if (!is_object($response)) {
399+
throw new RuntimeException('Invalid response');
400+
}
401+
402+
return TokenResponse::FromObject($response);
403+
}
404+
405+
/**
406+
* Revoke a JWT token
407+
*
408+
* @param string $jti
409+
* @return void
410+
*/
411+
public function RevokeToken(string $jti): void {
412+
$path = '/auth/token/' . $jti;
413+
414+
$this->sendRequest(
415+
'DELETE',
416+
$path
417+
);
418+
}
419+
382420
/**
383421
* @param \AndroidSmsGateway\Interfaces\SerializableInterface|null $payload
384422
* @throws \Http\Client\Exception\HttpException

src/Domain/TokenRequest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace AndroidSmsGateway\Domain;
4+
5+
use AndroidSmsGateway\Interfaces\SerializableInterface;
6+
7+
class TokenRequest implements SerializableInterface {
8+
/** @var string[] */
9+
private array $scopes;
10+
private ?int $ttl;
11+
12+
/**
13+
* @param string[] $scopes
14+
* @param int|null $ttl
15+
*/
16+
public function __construct(array $scopes, ?int $ttl = null) {
17+
$this->scopes = $scopes;
18+
$this->ttl = $ttl;
19+
}
20+
21+
/**
22+
* @return string[]
23+
*/
24+
public function Scopes(): array {
25+
return $this->scopes;
26+
}
27+
28+
/**
29+
* @param string[] $scopes
30+
* @return self
31+
*/
32+
public function setScopes(array $scopes): self {
33+
$this->scopes = $scopes;
34+
return $this;
35+
}
36+
37+
public function TTL(): ?int {
38+
return $this->ttl;
39+
}
40+
41+
public function setTtl(?int $ttl): self {
42+
$this->ttl = $ttl;
43+
return $this;
44+
}
45+
46+
public function toObject(): \stdClass {
47+
$obj = new \stdClass();
48+
$obj->scopes = $this->scopes;
49+
50+
if ($this->ttl !== null) {
51+
$obj->ttl = $this->ttl;
52+
}
53+
54+
return $obj;
55+
}
56+
57+
/**
58+
* @param object $obj
59+
* @return self
60+
*/
61+
public static function FromObject(object $obj): self {
62+
return new self(
63+
$obj->scopes ?? [],
64+
$obj->ttl ?? null
65+
);
66+
}
67+
}

src/Domain/TokenResponse.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace AndroidSmsGateway\Domain;
4+
5+
use AndroidSmsGateway\Interfaces\SerializableInterface;
6+
7+
class TokenResponse implements SerializableInterface {
8+
private string $accessToken;
9+
private string $tokenType;
10+
private string $id;
11+
private string $expiresAt;
12+
13+
public function __construct(
14+
string $accessToken,
15+
string $tokenType,
16+
string $id,
17+
string $expiresAt
18+
) {
19+
$this->accessToken = $accessToken;
20+
$this->tokenType = $tokenType;
21+
$this->id = $id;
22+
$this->expiresAt = $expiresAt;
23+
}
24+
25+
public function AccessToken(): string {
26+
return $this->accessToken;
27+
}
28+
29+
public function setAccessToken(string $accessToken): self {
30+
$this->accessToken = $accessToken;
31+
return $this;
32+
}
33+
34+
public function TokenType(): string {
35+
return $this->tokenType;
36+
}
37+
38+
public function setTokenType(string $tokenType): self {
39+
$this->tokenType = $tokenType;
40+
return $this;
41+
}
42+
43+
public function ID(): string {
44+
return $this->id;
45+
}
46+
47+
public function setId(string $id): self {
48+
$this->id = $id;
49+
return $this;
50+
}
51+
52+
public function ExpiresAt(): string {
53+
return $this->expiresAt;
54+
}
55+
56+
public function setExpiresAt(string $expiresAt): self {
57+
$this->expiresAt = $expiresAt;
58+
return $this;
59+
}
60+
61+
public function toObject(): \stdClass {
62+
$obj = new \stdClass();
63+
$obj->access_token = $this->accessToken;
64+
$obj->token_type = $this->tokenType;
65+
$obj->id = $this->id;
66+
$obj->expires_at = $this->expiresAt;
67+
68+
return $obj;
69+
}
70+
71+
/**
72+
* @param object $obj
73+
* @return self
74+
*/
75+
public static function FromObject(object $obj): self {
76+
return new self(
77+
$obj->access_token ?? '',
78+
$obj->token_type ?? '',
79+
$obj->id ?? '',
80+
$obj->expires_at ?? ''
81+
);
82+
}
83+
}

tests/ClientTest.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use AndroidSmsGateway\Domain\MessageState;
99
use AndroidSmsGateway\Domain\Settings;
1010
use AndroidSmsGateway\Domain\Webhook;
11+
use AndroidSmsGateway\Domain\TokenRequest;
12+
use AndroidSmsGateway\Domain\TokenResponse;
1113
use AndroidSmsGateway\Enums\ProcessState;
1214
use AndroidSmsGateway\Enums\WebhookEvent;
1315
use Http\Client\Curl\Client as CurlClient;
@@ -384,6 +386,72 @@ public function testDeleteWebhook(): void {
384386
);
385387
}
386388

389+
public function testGenerateToken(): void {
390+
$tokenRequest = new TokenRequest(['read', 'write'], 3600);
391+
392+
$responseMock = self::mockResponse(
393+
'{"access_token":"test-token","token_type":"Bearer","id":"token-id","expires_at":"2023-12-31T23:59:59Z"}',
394+
201,
395+
['Content-Type' => 'application/json']
396+
);
397+
398+
$this->mockClient->addResponse($responseMock);
399+
400+
$tokenResponse = $this->client->GenerateToken($tokenRequest);
401+
$req = $this->mockClient->getLastRequest();
402+
$this->assertEquals('POST', $req->getMethod());
403+
$this->assertEquals('/3rdparty/v1/auth/token', $req->getUri()->getPath());
404+
$this->assertEquals(
405+
'Basic ' . base64_encode(self::MOCK_LOGIN . ':' . self::MOCK_PASSWORD),
406+
$req->getHeaderLine('Authorization')
407+
);
408+
$this->assertEquals(
409+
'application/json',
410+
$req->getHeaderLine('Content-Type')
411+
);
412+
413+
$this->assertInstanceOf(TokenResponse::class, $tokenResponse);
414+
$this->assertEquals('test-token', $tokenResponse->AccessToken());
415+
$this->assertEquals('Bearer', $tokenResponse->TokenType());
416+
$this->assertEquals('token-id', $tokenResponse->ID());
417+
$this->assertEquals('2023-12-31T23:59:59Z', $tokenResponse->ExpiresAt());
418+
}
419+
420+
public function testRevokeToken(): void {
421+
$responseMock = self::mockResponse('', 204);
422+
423+
$this->mockClient->addResponse($responseMock);
424+
425+
$this->client->RevokeToken('token-id');
426+
$req = $this->mockClient->getLastRequest();
427+
$this->assertEquals('DELETE', $req->getMethod());
428+
$this->assertEquals('/3rdparty/v1/auth/token/token-id', $req->getUri()->getPath());
429+
$this->assertEquals(
430+
'Basic ' . base64_encode(self::MOCK_LOGIN . ':' . self::MOCK_PASSWORD),
431+
$req->getHeaderLine('Authorization')
432+
);
433+
}
434+
435+
public function testClientWithJwtToken(): void {
436+
$jwtToken = 'test-jwt-token';
437+
$client = new Client(null, $jwtToken, Client::DEFAULT_URL, $this->mockClient);
438+
439+
$responseMock = self::mockResponse(
440+
'{"id":"123","state":"Sent","recipients":[{"phoneNumber":"+79000000000","state":"Sent"}]}',
441+
201,
442+
['Content-Type' => 'application/json']
443+
);
444+
445+
$this->mockClient->addResponse($responseMock);
446+
447+
$messageMock = $this->createMock(Message::class);
448+
$messageMock->method('ToObject')->willReturn((object) []);
449+
450+
$client->SendMessage($messageMock);
451+
$req = $this->mockClient->getLastRequest();
452+
$this->assertEquals('Bearer ' . $jwtToken, $req->getHeaderLine('Authorization'));
453+
}
454+
387455
public const MOCK_LOGIN = 'login';
388456
public const MOCK_PASSWORD = 'password';
389457
}

tests/Domain/TokenRequestTest.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
namespace AndroidSmsGateway\Tests\Domain;
4+
5+
use AndroidSmsGateway\Domain\TokenRequest;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class TokenRequestTest extends TestCase {
9+
public function testTokenRequestCreation(): void {
10+
$scopes = ['read', 'write'];
11+
$ttl = 3600;
12+
13+
$tokenRequest = new TokenRequest($scopes, $ttl);
14+
15+
$this->assertEquals($scopes, $tokenRequest->Scopes());
16+
$this->assertEquals($ttl, $tokenRequest->TTL());
17+
}
18+
19+
public function testTokenRequestCreationWithoutTtl(): void {
20+
$scopes = ['read'];
21+
22+
$tokenRequest = new TokenRequest($scopes);
23+
24+
$this->assertEquals($scopes, $tokenRequest->Scopes());
25+
$this->assertNull($tokenRequest->TTL());
26+
}
27+
28+
public function testTokenRequestSetters(): void {
29+
$tokenRequest = new TokenRequest(['read']);
30+
31+
$newScopes = ['read', 'write', 'admin'];
32+
$newTtl = 7200;
33+
34+
$tokenRequest->setScopes($newScopes);
35+
$tokenRequest->setTtl($newTtl);
36+
37+
$this->assertEquals($newScopes, $tokenRequest->Scopes());
38+
$this->assertEquals($newTtl, $tokenRequest->TTL());
39+
}
40+
41+
public function testTokenRequestToObject(): void {
42+
$scopes = ['read', 'write'];
43+
$ttl = 3600;
44+
45+
$tokenRequest = new TokenRequest($scopes, $ttl);
46+
$obj = $tokenRequest->toObject();
47+
48+
$this->assertEquals($scopes, $obj->scopes);
49+
$this->assertEquals($ttl, $obj->ttl);
50+
}
51+
52+
public function testTokenRequestToObjectWithoutTtl(): void {
53+
$scopes = ['read'];
54+
55+
$tokenRequest = new TokenRequest($scopes);
56+
$obj = $tokenRequest->toObject();
57+
58+
$this->assertEquals($scopes, $obj->scopes);
59+
$this->assertObjectNotHasProperty('ttl', $obj);
60+
}
61+
62+
public function testTokenRequestFromObject(): void {
63+
$obj = new \stdClass();
64+
$obj->scopes = ['read', 'write'];
65+
$obj->ttl = 3600;
66+
67+
$tokenRequest = TokenRequest::FromObject($obj);
68+
69+
$this->assertEquals($obj->scopes, $tokenRequest->Scopes());
70+
$this->assertEquals($obj->ttl, $tokenRequest->TTL());
71+
}
72+
73+
public function testTokenRequestFromObjectWithoutTtl(): void {
74+
$obj = new \stdClass();
75+
$obj->scopes = ['read'];
76+
77+
$tokenRequest = TokenRequest::FromObject($obj);
78+
79+
$this->assertEquals($obj->scopes, $tokenRequest->Scopes());
80+
$this->assertNull($tokenRequest->TTL());
81+
}
82+
83+
public function testTokenRequestFromObjectWithDefaultValues(): void {
84+
$obj = new \stdClass();
85+
86+
$tokenRequest = TokenRequest::FromObject($obj);
87+
88+
$this->assertEquals([], $tokenRequest->Scopes());
89+
$this->assertNull($tokenRequest->TTL());
90+
}
91+
}

0 commit comments

Comments
 (0)