Skip to content

Commit 2533d35

Browse files
authored
feat: merge OAuth support into main (#854)
1 parent 6373585 commit 2533d35

File tree

68 files changed

+4779
-27
lines changed

Some content is hidden

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

68 files changed

+4779
-27
lines changed

.github/workflows/test-and-deploy.yml

+6
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ jobs:
5757
TWILIO_API_SECRET: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY_SECRET }}
5858
TWILIO_FROM_NUMBER: ${{ secrets.TWILIO_FROM_NUMBER }}
5959
TWILIO_TO_NUMBER: ${{ secrets.TWILIO_TO_NUMBER }}
60+
TWILIO_ORGS_CLIENT_ID: ${{ secrets.TWILIO_ORGS_CLIENT_ID }}
61+
TWILIO_ORGS_CLIENT_SECRET: ${{ secrets.TWILIO_ORGS_CLIENT_SECRET }}
62+
TWILIO_ORG_SID: ${{ secrets.TWILIO_ORG_SID }}
63+
TWILIO_CLIENT_ID: ${{ secrets.TWILIO_CLIENT_ID }}
64+
TWILIO_CLIENT_SECRET: ${{ secrets.TWILIO_CLIENT_SECRET }}
65+
TWILIO_MESSAGE_SID: ${{ secrets.TWILIO_MESSAGE_SID }}
6066
run: make cluster-test
6167

6268
- name: Install SonarCloud scanner and run analysis

example/orgs_api_example.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
require(__DIR__.'/../src/Twilio/autoload.php');
3+
4+
use Twilio\Rest\Client;
5+
use Twilio\CredentialProvider\OrgsCredentialProviderBuilder;
6+
7+
$clientId = getenv('ORGS_CLIENT_ID');
8+
$clientSecret = getenv('ORGS_CLIENT_SECRET');
9+
$orgSid = getenv('ORG_SID');
10+
11+
$orgsCredentialProvider = (new OrgsCredentialProviderBuilder())->setClientId($clientId)->setClientSecret($clientSecret)->build();
12+
13+
$client = new Client();
14+
$client->setCredentialProvider($orgsCredentialProvider);
15+
16+
//list users
17+
$users = $client->previewIam->organization($orgSid)->users->read();
18+
foreach ($users as $user) {
19+
printf("User SID: %s\n", $user->id);
20+
}

example/public_oauth_example.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
require(__DIR__.'/../src/Twilio/autoload.php');
3+
4+
use Twilio\Rest\Client;
5+
use Twilio\CredentialProvider\ClientCredentialProviderBuilder;
6+
7+
$accountSid = getenv('TWILIO_ACCOUNT_SID');
8+
$token = getenv('TWILIO_AUTH_TOKEN');
9+
10+
$clientId = getenv('OAUTH_CLIENT_ID');
11+
$clientSecret = getenv('OAUTH_CLIENT_SECRET');
12+
13+
$clientCredentialProvider = (new ClientCredentialProviderBuilder())->setClientId($clientId)->setClientSecret($clientSecret)->build();
14+
15+
$client = new Client();
16+
$client->setCredentialProvider($clientCredentialProvider);
17+
$client->setAccountSid($accountSid);
18+
19+
$messageList = $client->messages->read([],10);
20+
foreach ($messageList as $msg) {
21+
print("ID:: ". $msg->sid . " | " . "From:: " . $msg->from . " | " . "TO:: " . $msg->to . " | " . " Status:: " . $msg->status . " | " . " Body:: ". $msg->body ."\n");
22+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
namespace Twilio\AuthStrategy;
3+
4+
/**
5+
* Class AuthStrategy
6+
* Abstract parent class for all authentication strategies - Basic, Bearer Token, NoAuth etc.
7+
* @property string $authType The type of authentication strategy
8+
*/
9+
10+
abstract class AuthStrategy {
11+
private $authType;
12+
13+
public function __construct(string $authType) {
14+
$this->authType = $authType;
15+
}
16+
17+
public function getAuthType(): string {
18+
return $this->authType;
19+
}
20+
21+
/**
22+
* Returns the value to be set in the authentication header
23+
*
24+
* @return string the authentication string
25+
*/
26+
abstract public function getAuthString(): string;
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
namespace Twilio\AuthStrategy;
3+
4+
/**
5+
* Class BasicAuthStrategy
6+
* Implementation of the AuthStrategy for Basic authentication
7+
* @property string $username
8+
* @property string $password
9+
*/
10+
11+
class BasicAuthStrategy extends AuthStrategy {
12+
private $username;
13+
private $password;
14+
15+
public function __construct(string $username, string $password) {
16+
parent::__construct("basic");
17+
$this->username = $username;
18+
$this->password = $password;
19+
}
20+
21+
/**
22+
* Returns the base64 encoded string concatenating the username and password
23+
*
24+
* @return string the base64 encoded string
25+
*/
26+
public function getAuthString(): string {
27+
return base64_encode($this->username . ':' . $this->password);
28+
}
29+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
namespace Twilio\AuthStrategy;
3+
4+
/**
5+
* Class NoAuthStrategy
6+
* Implementation of the AuthStrategy for No Authentication
7+
*/
8+
9+
class NoAuthStrategy extends AuthStrategy {
10+
11+
public function __construct() {
12+
parent::__construct("noauth");
13+
}
14+
15+
/**
16+
* Returns an empty string since no authentication is required
17+
*
18+
* @return string an empty string
19+
*/
20+
public function getAuthString(): string {
21+
return "";
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
namespace Twilio\AuthStrategy;
3+
4+
use Twilio\Rest\Client;
5+
use Twilio\Http\BearerToken\TokenManager;
6+
/**
7+
* Class TokenAuthStrategy
8+
* Implementation of the AuthStrategy for Bearer Token Authentication
9+
* @property string $token The bearer token
10+
* @property TokenManager $tokenManager The manager for the bearer token
11+
*/
12+
13+
class TokenAuthStrategy extends AuthStrategy {
14+
private $token;
15+
private $tokenManager;
16+
17+
public function __construct(TokenManager $tokenManager) {
18+
parent::__construct("token");
19+
$this->tokenManager = $tokenManager;
20+
}
21+
22+
/**
23+
* Checks if the token is expired or not
24+
*
25+
* @param string $token the token to be checked
26+
*
27+
* @return bool whether the token is expired or not
28+
*/
29+
public function isTokenExpired(string $token): bool {
30+
// Decode the JWT token
31+
$decodedToken = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $token)[1]))), true);
32+
33+
$expireField = $decodedToken['exp'];
34+
35+
// If the token doesn't have an expiration, consider it expired
36+
if ($decodedToken === null || $expireField === null) {
37+
return false;
38+
}
39+
40+
// Calculate the expiration time with a buffer of 30 seconds
41+
$expiresAt = $expireField * 1000;
42+
$bufferMilliseconds = 30 * 1000;
43+
$bufferExpiresAt = $expiresAt - $bufferMilliseconds;
44+
45+
// Return true if the current time is after the expiration time with buffer
46+
return round(microtime(true)*1000) > $bufferExpiresAt;
47+
}
48+
49+
/**
50+
* Fetches the bearer token
51+
*
52+
* @return string the bearer token
53+
*/
54+
public function fetchToken(?Client $client = null): string {
55+
if (empty($this->token) || $this->isTokenExpired($this->token)) {
56+
$this->token = $this->tokenManager->fetchToken($client);
57+
}
58+
return $this->token;
59+
}
60+
61+
/**
62+
* Returns the bearer token authentication string
63+
*
64+
* @return string the bearer token authentication string
65+
*/
66+
public function getAuthString(?Client $client = null): string {
67+
return "Bearer " . $this->fetchToken($client);
68+
}
69+
}

src/Twilio/Base/BaseClient.php

+60-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<?php
22
namespace Twilio\Base;
33

4+
use Twilio\AuthStrategy\AuthStrategy;
5+
use Twilio\CredentialProvider\CredentialProvider;
46
use Twilio\Exceptions\ConfigurationException;
57
use Twilio\Exceptions\TwilioException;
68
use Twilio\Http\Client as HttpClient;
@@ -24,6 +26,7 @@ class BaseClient
2426

2527
protected $username;
2628
protected $password;
29+
protected $credentialProvider;
2730
protected $accountSid;
2831
protected $region;
2932
protected $edge;
@@ -46,7 +49,6 @@ class BaseClient
4649
* @param mixed[] $environment Environment to look for auth details, defaults
4750
* to $_ENV
4851
* @param string[] $userAgentExtensions Additions to the user agent string
49-
* @throws ConfigurationException If valid authentication is not present
5052
*/
5153
public function __construct(
5254
?string $username = null,
@@ -59,18 +61,15 @@ public function __construct(
5961
) {
6062
$this->environment = $environment ?: \getenv();
6163

62-
$this->username = $this->getArg($username, self::ENV_ACCOUNT_SID);
63-
$this->password = $this->getArg($password, self::ENV_AUTH_TOKEN);
64+
$this->setUsername($this->getArg($username, self::ENV_ACCOUNT_SID));
65+
$this->setPassword($this->getArg($password, self::ENV_AUTH_TOKEN));
6466
$this->region = $this->getArg($region, self::ENV_REGION);
6567
$this->edge = $this->getArg(null, self::ENV_EDGE);
6668
$this->logLevel = $this->getArg(null, self::ENV_LOG);
6769
$this->userAgentExtensions = $userAgentExtensions ?: [];
6870

69-
if (!$this->username || !$this->password) {
70-
throw new ConfigurationException('Credentials are required to create a Client');
71-
}
72-
73-
$this->accountSid = $accountSid ?: $this->username;
71+
$this->invalidateOAuth();
72+
$this->setAccountSid($accountSid ?: $this->username);
7473

7574
if ($httpClient) {
7675
$this->httpClient = $httpClient;
@@ -79,6 +78,36 @@ public function __construct(
7978
}
8079
}
8180

81+
public function setUsername(?string $username): void {
82+
$this->username = $username;
83+
}
84+
85+
public function setPassword(?string $password): void {
86+
$this->password = $password;
87+
}
88+
89+
public function setAccountSid(?string $accountSid): void {
90+
$this->accountSid = $accountSid;
91+
}
92+
93+
private function _setCredentialProvider($credentialProvider): void {
94+
$this->credentialProvider = $credentialProvider;
95+
}
96+
97+
public function setCredentialProvider(CredentialProvider $credentialProvider): void {
98+
$this->_setCredentialProvider($credentialProvider);
99+
$this->invalidateBasicAuth();
100+
}
101+
102+
public function invalidateBasicAuth(): void {
103+
$this->setUsername("");
104+
$this->setPassword("");
105+
}
106+
107+
public function invalidateOAuth(): void {
108+
$this->_setCredentialProvider(null);
109+
}
110+
82111
/**
83112
* Determines argument value accounting for environment variables.
84113
*
@@ -111,7 +140,9 @@ public function getArg(?string $arg, string $envVar): ?string
111140
* @param string $username User for Authentication
112141
* @param string $password Password for Authentication
113142
* @param int $timeout Timeout in seconds
143+
* @param AuthStrategy $authStrategy AuthStrategy for Authentication
114144
* @return \Twilio\Http\Response Response from the Twilio API
145+
* @throws TwilioException
115146
*/
116147
public function request(
117148
string $method,
@@ -121,10 +152,26 @@ public function request(
121152
array $headers = [],
122153
?string $username = null,
123154
?string $password = null,
124-
?int $timeout = null
155+
?int $timeout = null,
156+
?AuthStrategy $authStrategy = null
125157
): \Twilio\Http\Response{
126158
$username = $username ?: $this->username;
127159
$password = $password ?: $this->password;
160+
$authStrategy = $authStrategy ?: null;
161+
if ($this->credentialProvider) {
162+
$authStrategy = $this->credentialProvider->toAuthStrategy();
163+
}
164+
165+
if (!$authStrategy) {
166+
if (!$username) {
167+
throw new ConfigurationException('username is required');
168+
}
169+
170+
if (!$password) {
171+
throw new ConfigurationException("password is required");
172+
}
173+
}
174+
128175
$logLevel = (getenv('DEBUG_HTTP_TRAFFIC') === 'true' ? 'debug' : $this->getLogLevel());
129176

130177
$headers['User-Agent'] = 'twilio-php/' . VersionInfo::string() .
@@ -136,7 +183,8 @@ public function request(
136183
$headers['User-Agent'] .= ' ' . implode(' ', $this->userAgentExtensions);
137184
}
138185

139-
if (!\array_key_exists('Accept', $headers)) {
186+
// skip adding Accept header in case of delete operation
187+
if ($method !== "DELETE" && !\array_key_exists('Accept', $headers)) {
140188
$headers['Accept'] = 'application/json';
141189
}
142190

@@ -169,7 +217,8 @@ public function request(
169217
$headers,
170218
$username,
171219
$password,
172-
$timeout
220+
$timeout,
221+
$authStrategy
173222
);
174223

175224
if ($logLevel === 'debug') {

0 commit comments

Comments
 (0)