Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "developersrede/erede-php",
"version": "5.2.1",
"name": "robsondrs/erede-php",
"version": "5.2.5",
"description": "e.Rede integration SDK",
"minimum-stability": "stable",
"license": "MIT",
"type": "library",
"require": {
"php": "^8.1",
"php": "^7.3",
"ext-curl": "*",
"ext-json": "*",
"psr/log": "*",
Expand All @@ -28,6 +28,10 @@
{
"name": "João Batista Neto",
"email": "neto.joaobatista@gmail.com"
},
{
"name": "Robson Soares",
"email": "robson_drs@hotmail.com"
}
]
}
37 changes: 37 additions & 0 deletions src/Rede/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ class Environment implements RedeSerializable
public const SANDBOX = 'https://api.userede.com.br/desenvolvedores';
public const VERSION = 'v1';

/**
* OAuth2 token endpoints
*/
public const OAUTH_TOKEN_PRODUCTION = 'https://api.userede.com.br/redelabs/oauth2/token';
public const OAUTH_TOKEN_SANDBOX = 'https://rl7-sandbox-api.useredecloud.com.br/oauth2/token';

/**
* @var string|null
*/
Expand All @@ -25,6 +31,11 @@ class Environment implements RedeSerializable
*/
private string $endpoint;

/**
* @var string OAuth2 token endpoint URL
*/
private string $oauthTokenUrl;

/**
* Creates an environment with its base url and version
*
Expand All @@ -33,6 +44,14 @@ class Environment implements RedeSerializable
private function __construct(string $baseUrl)
{
$this->endpoint = sprintf('%s/%s/', $baseUrl, Environment::VERSION);

if ($baseUrl === Environment::PRODUCTION) {
$this->oauthTokenUrl = Environment::OAUTH_TOKEN_PRODUCTION;
} elseif ($baseUrl === Environment::SANDBOX) {
$this->oauthTokenUrl = Environment::OAUTH_TOKEN_SANDBOX;
} else {
$this->oauthTokenUrl = rtrim($baseUrl, '/') . '/oauth2/token';
}
}

/**
Expand Down Expand Up @@ -61,6 +80,24 @@ public function getEndpoint(string $service): string
return $this->endpoint . $service;
}

/**
* @return string OAuth2 token endpoint URL
*/
public function getOAuthTokenUrl(): string
{
return $this->oauthTokenUrl;
}

/**
* @param string $oauthTokenUrl
* @return $this
*/
public function setOAuthTokenUrl(string $oauthTokenUrl): static
{
$this->oauthTokenUrl = $oauthTokenUrl;
return $this;
}

/**
* @return string|null
*/
Expand Down
96 changes: 91 additions & 5 deletions src/Rede/Service/AbstractService.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ protected function sendRequest(string $body = '', string $method = 'GET'): Trans
throw new RuntimeException('Was not possible to create a curl instance.');
}

curl_setopt(
$curl,
CURLOPT_USERPWD,
sprintf('%s:%s', $this->store->getFiliation(), $this->store->getToken())
);
// OAuth2 Bearer Token authentication flow
$bearer = $this->ensureBearerToken();
if ($bearer !== null) {
$headers[] = 'Authorization: Bearer ' . $bearer;
}

curl_setopt($curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
Expand Down Expand Up @@ -223,4 +223,90 @@ private function dumpHttpInfo(array $httpInfo): void
* @return Transaction
*/
abstract protected function parseResponse(string $response, int $statusCode): Transaction;

/**
* Ensures a valid Bearer Token, obtaining a new one via OAuth2 when needed.
*
* @return string|null
*/
private function ensureBearerToken(): ?string
{
$currentToken = $this->store->getBearerToken();
$expiresAt = $this->store->getBearerTokenExpiresAt();

$now = time();
if ($currentToken !== null && is_int($expiresAt) && $expiresAt > ($now + 60)) {
return $currentToken;
}

return $this->requestBearerToken();
}

/**
* Requests a new token via OAuth2 (client_credentials) and stores it in the Store.
*
* @return string|null
*/
private function requestBearerToken(): ?string
{
$env = $this->store->getEnvironment();
$tokenUrl = $env->getOAuthTokenUrl();

$this->logger?->debug(sprintf('Requesting OAuth2 token at %s', $tokenUrl));

$curl = curl_init($tokenUrl);
if (!$curl instanceof CurlHandle) {
throw new RuntimeException('Was not possible to create a curl instance for OAuth token.');
}

$basic = base64_encode(sprintf('%s:%s', $this->store->getFiliation(), $this->store->getToken()));

$headers = [
'Accept: application/json',
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Basic ' . $basic,
];

curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query(['grant_type' => 'client_credentials']));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);

$response = curl_exec($curl);
$httpInfo = curl_getinfo($curl);

if (curl_errno($curl)) {
$errorMessage = sprintf('Curl error on OAuth token request[%s]: %s', curl_errno($curl), curl_error($curl));
curl_close($curl);
throw new RuntimeException($errorMessage);
}

curl_close($curl);

if (!is_string($response)) {
throw new RuntimeException('Invalid OAuth token response');
}

$this->logger?->debug(sprintf("OAuth token response status=%s body=%s", $httpInfo['http_code'] ?? 'n/a', $response));

$decoded = json_decode($response, true);
if (!is_array($decoded)) {
throw new RuntimeException('Unable to parse OAuth token response');
}

$accessToken = $decoded['access_token'] ?? null;
$expiresIn = $decoded['expires_in'] ?? 0;

if (!is_string($accessToken) || $accessToken === '') {
$errorDescription = $decoded['error_description'] ?? $decoded['error'] ?? 'Unknown error obtaining access token';
throw new RuntimeException(sprintf('OAuth token error: %s', $errorDescription));
}

$expiresAt = time() + (is_int($expiresIn) ? $expiresIn : (int)$expiresIn) - 60; // margem de segurança
$this->store->setBearerToken($accessToken, $expiresAt);

return $accessToken;
}
}
40 changes: 40 additions & 0 deletions src/Rede/Store.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ class Store
*/
private Environment $environment;

/**
* @var string|null Bearer token obtained via OAuth2
*/
private ?string $bearerToken = null;

/**
* @var int|null Epoch expiration of the token
*/
private ?int $bearerTokenExpiresAt = null;

/**
* Creates a store.
*
Expand Down Expand Up @@ -78,4 +88,34 @@ public function setToken(string $token): static
$this->token = $token;
return $this;
}

/**
* Define the bearer token and the absolute expiration time (epoch).
*
* @param string $token
* @param int $expiresAtEpoch
* @return $this
*/
public function setBearerToken(string $token, int $expiresAtEpoch): static
{
$this->bearerToken = $token;
$this->bearerTokenExpiresAt = $expiresAtEpoch;
return $this;
}

/**
* @return string|null
*/
public function getBearerToken(): ?string
{
return $this->bearerToken;
}

/**
* @return int|null
*/
public function getBearerTokenExpiresAt(): ?int
{
return $this->bearerTokenExpiresAt;
}
}