208 lines
6.2 KiB
PHP
208 lines
6.2 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace Lib\Google;
|
||
|
|
|
||
|
|
class GoogleOAuth2 {
|
||
|
|
private string $clientId;
|
||
|
|
private string $clientSecret;
|
||
|
|
private string $redirectUri;
|
||
|
|
private array $scopes = [];
|
||
|
|
private ?string $accessToken = null;
|
||
|
|
private ?string $refreshToken = null;
|
||
|
|
private ?int $tokenExpires = null;
|
||
|
|
|
||
|
|
private const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||
|
|
private const TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
||
|
|
|
||
|
|
public function __construct(string $clientId, string $clientSecret, string $redirectUri) {
|
||
|
|
$this->clientId = $clientId;
|
||
|
|
$this->clientSecret = $clientSecret;
|
||
|
|
$this->redirectUri = $redirectUri;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add scopes for the OAuth2 request
|
||
|
|
* @param array $scopes Array of Google OAuth2 scopes
|
||
|
|
* @return self
|
||
|
|
*/
|
||
|
|
public function addScopes(array $scopes): self {
|
||
|
|
$this->scopes = array_merge($this->scopes, $scopes);
|
||
|
|
return $this;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate the authorization URL
|
||
|
|
* @param array $additionalParams Additional URL parameters
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function getAuthorizationUrl(array $additionalParams = []): string {
|
||
|
|
$params = array_merge([
|
||
|
|
'client_id' => $this->clientId,
|
||
|
|
'redirect_uri' => $this->redirectUri,
|
||
|
|
'response_type' => 'code',
|
||
|
|
'access_type' => 'offline',
|
||
|
|
'prompt' => 'consent',
|
||
|
|
'scope' => implode(' ', array_unique($this->scopes))
|
||
|
|
], $additionalParams);
|
||
|
|
|
||
|
|
return self::AUTH_URL . '?' . http_build_query($params);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Exchange authorization code for tokens
|
||
|
|
* @param string $code Authorization code
|
||
|
|
* @return array Token response
|
||
|
|
* @throws \Exception
|
||
|
|
*/
|
||
|
|
public function exchangeCode(string $code): array {
|
||
|
|
$response = $this->makeTokenRequest([
|
||
|
|
'grant_type' => 'authorization_code',
|
||
|
|
'code' => $code,
|
||
|
|
'redirect_uri' => $this->redirectUri
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->setTokens($response);
|
||
|
|
return $response;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Refresh the access token
|
||
|
|
* @throws \Exception
|
||
|
|
*/
|
||
|
|
public function refreshAccessToken(): array {
|
||
|
|
if (!$this->refreshToken) {
|
||
|
|
throw new \Exception('No refresh token available');
|
||
|
|
}
|
||
|
|
|
||
|
|
$response = $this->makeTokenRequest([
|
||
|
|
'grant_type' => 'refresh_token',
|
||
|
|
'refresh_token' => $this->refreshToken
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->setTokens($response);
|
||
|
|
return $response;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Make an authenticated request to Google APIs
|
||
|
|
* @param string $url
|
||
|
|
* @param string $method
|
||
|
|
* @param array $headers
|
||
|
|
* @param mixed $body
|
||
|
|
* @param bool $rawResponse Whether to return raw response instead of JSON
|
||
|
|
* @return array|string
|
||
|
|
* @throws \Exception
|
||
|
|
*/
|
||
|
|
public function makeAuthenticatedRequest(
|
||
|
|
string $url,
|
||
|
|
string $method = 'GET',
|
||
|
|
array $headers = [],
|
||
|
|
$body = null,
|
||
|
|
bool $rawResponse = false
|
||
|
|
){
|
||
|
|
if ($this->isTokenExpired()) {
|
||
|
|
$this->refreshAccessToken();
|
||
|
|
}
|
||
|
|
|
||
|
|
$headers = array_merge([
|
||
|
|
'Authorization: Bearer ' . $this->accessToken,
|
||
|
|
'Accept: application/json'
|
||
|
|
], $headers);
|
||
|
|
|
||
|
|
$ch = curl_init();
|
||
|
|
curl_setopt_array($ch, [
|
||
|
|
CURLOPT_URL => $url,
|
||
|
|
CURLOPT_RETURNTRANSFER => true,
|
||
|
|
CURLOPT_HTTPHEADER => $headers,
|
||
|
|
CURLOPT_CUSTOMREQUEST => $method
|
||
|
|
]);
|
||
|
|
|
||
|
|
if ($body && in_array($method, ['POST', 'PUT', 'PATCH'])) {
|
||
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
|
||
|
|
}
|
||
|
|
|
||
|
|
$response = curl_exec($ch);
|
||
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
|
|
curl_close($ch);
|
||
|
|
|
||
|
|
if ($httpCode >= 400) {
|
||
|
|
throw new \Exception('Request failed with status ' . $httpCode . ': ' . $response);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $rawResponse ? $response : json_decode($response, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set tokens from OAuth2 response
|
||
|
|
* @param array $response
|
||
|
|
*/
|
||
|
|
private function setTokens(array $response): void {
|
||
|
|
$this->accessToken = $response['access_token'];
|
||
|
|
if (isset($response['refresh_token'])) {
|
||
|
|
$this->refreshToken = $response['refresh_token'];
|
||
|
|
}
|
||
|
|
$this->tokenExpires = time() + ($response['expires_in'] ?? 3600);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if the current access token is expired
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
private function isTokenExpired(): bool {
|
||
|
|
return !$this->accessToken || !$this->tokenExpires || time() >= $this->tokenExpires;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Make a token request to Google's OAuth2 server
|
||
|
|
* @param array $params
|
||
|
|
* @return array
|
||
|
|
* @throws \Exception
|
||
|
|
*/
|
||
|
|
private function makeTokenRequest(array $params): array {
|
||
|
|
$params = array_merge([
|
||
|
|
'client_id' => $this->clientId,
|
||
|
|
'client_secret' => $this->clientSecret
|
||
|
|
], $params);
|
||
|
|
|
||
|
|
$ch = curl_init();
|
||
|
|
curl_setopt_array($ch, [
|
||
|
|
CURLOPT_URL => self::TOKEN_URL,
|
||
|
|
CURLOPT_RETURNTRANSFER => true,
|
||
|
|
CURLOPT_POST => true,
|
||
|
|
CURLOPT_POSTFIELDS => http_build_query($params),
|
||
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded']
|
||
|
|
]);
|
||
|
|
|
||
|
|
$response = curl_exec($ch);
|
||
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
|
|
curl_close($ch);
|
||
|
|
|
||
|
|
if ($httpCode !== 200) {
|
||
|
|
throw new \Exception('Token request failed with status ' . $httpCode . ': ' . $response);
|
||
|
|
}
|
||
|
|
|
||
|
|
return json_decode($response, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Getters for tokens and expiration
|
||
|
|
public function getAccessToken(): ?string {
|
||
|
|
return $this->accessToken;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setAccessToken(string $accessToken): void {
|
||
|
|
$this->accessToken = $accessToken;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getRefreshToken(): ?string {
|
||
|
|
return $this->refreshToken;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setRefreshToken(string $refreshToken): void {
|
||
|
|
$this->refreshToken = $refreshToken;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getTokenExpires(): ?int {
|
||
|
|
return $this->tokenExpires;
|
||
|
|
}
|
||
|
|
}
|