Files
crm-ms1/v4_ci4/app/Libraries/Radar/GoogleWorkspaceToken.php

175 lines
5.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Libraries\Radar;
/**
* JWT + access token Google Workspace (Domain-Wide Delegation).
* Même pattern que application/libraries/GCalService.php (MSOP-4).
*/
class GoogleWorkspaceToken
{
private string $jsonPath;
private string $tokenUri = 'https://oauth2.googleapis.com/token';
public function __construct(string $jsonPath)
{
$this->jsonPath = $jsonPath;
if (! \is_file($this->jsonPath)) {
throw new \RuntimeException('Service account JSON introuvable: ' . $this->jsonPath);
}
}
public function getAccessToken(string $impersonateEmail, string $scopes): string
{
$creds = \json_decode((string) \file_get_contents($this->jsonPath), true);
if (! \is_array($creds) || empty($creds['client_email']) || empty($creds['private_key'])) {
throw new \RuntimeException('service_account.json invalide');
}
$now = \time();
$header = ['alg' => 'RS256', 'typ' => 'JWT'];
$claim = [
'iss' => $creds['client_email'],
'scope' => $scopes,
'aud' => $this->tokenUri,
'exp' => $now + 3600,
'iat' => $now,
'sub' => $impersonateEmail,
];
$unsigned = $this->b64url(\json_encode($header)) . '.' . $this->b64url(\json_encode($claim));
$signature = '';
if (! \openssl_sign($unsigned, $signature, $creds['private_key'], \OPENSSL_ALGO_SHA256)) {
throw new \RuntimeException('openssl_sign failed');
}
$jwt = $unsigned . '.' . $this->b64url($signature);
[$code, $body] = $this->http(
'POST',
$this->tokenUri,
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $jwt,
])
);
if ($code !== 200) {
throw new \RuntimeException("Token Google error ({$code}): {$body}");
}
$tok = \json_decode($body, true);
if (empty($tok['access_token'])) {
throw new \RuntimeException('Pas daccess_token dans la réponse Google');
}
return $tok['access_token'];
}
/**
* HTTP sans dépendre de lext curl (souvent absente en cPanel).
*
* @param list<string> $headers
* @return array{0:int,1:string}
*/
public function http(string $method, string $url, array $headers = [], ?string $body = null): array
{
if (\function_exists('curl_init')) {
return $this->httpCurl($method, $url, $headers, $body);
}
return $this->httpStream($method, $url, $headers, $body);
}
/**
* @param list<string> $headers
* @return array{0:int,1:string}
*/
private function httpCurl(string $method, string $url, array $headers, ?string $body): array
{
$ch = \curl_init($url);
$opts = [
\CURLOPT_RETURNTRANSFER => true,
\CURLOPT_CUSTOMREQUEST => $method,
\CURLOPT_CONNECTTIMEOUT => 8,
\CURLOPT_TIMEOUT => 25,
\CURLOPT_HTTPHEADER => $headers,
// cPanel : IPv6 cassé → hang / 0 bytes. Forcer IPv4 (comme AiHttp).
\CURLOPT_IPRESOLVE => \CURL_IPRESOLVE_V4,
];
if ($body !== null) {
$opts[\CURLOPT_POSTFIELDS] = $body;
}
\curl_setopt_array($ch, $opts);
$res = \curl_exec($ch);
$code = (int) \curl_getinfo($ch, \CURLINFO_HTTP_CODE);
if ($res === false) {
$err = \curl_error($ch);
\curl_close($ch);
throw new \RuntimeException('cURL error: ' . $err);
}
\curl_close($ch);
return [$code, $res];
}
/**
* @param list<string> $headers
* @return array{0:int,1:string}
*/
private function httpStream(string $method, string $url, array $headers, ?string $body): array
{
$headerLines = $headers;
if ($body !== null && $body !== '') {
$hasContentType = false;
foreach ($headerLines as $h) {
if (\stripos($h, 'Content-Type:') === 0) {
$hasContentType = true;
break;
}
}
if (! $hasContentType) {
$headerLines[] = 'Content-Type: application/x-www-form-urlencoded';
}
}
$opts = [
'http' => [
'method' => \strtoupper($method),
'header' => \implode("\r\n", $headerLines),
'timeout' => 60,
'ignore_errors' => true,
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
];
if ($body !== null) {
$opts['http']['content'] = $body;
}
$ctx = \stream_context_create($opts);
$res = @\file_get_contents($url, false, $ctx);
$code = 0;
if (isset($http_response_header) && \is_array($http_response_header)) {
foreach ($http_response_header as $line) {
if (\preg_match('#^HTTP/\S+\s+(\d+)#', $line, $m)) {
$code = (int) $m[1];
break;
}
}
}
if ($res === false) {
throw new \RuntimeException('HTTP stream error for ' . $url);
}
return [$code, $res];
}
private function b64url(string $data): string
{
return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
}
}