CI =& get_instance(); $cfg = $this->CI->config->item('google'); if (!$cfg || empty($cfg['credentials_path'])) { throw new Exception('Config google.credentials_path manquante'); } $json = @file_get_contents($cfg['credentials_path']); if (!$json) { throw new Exception('Impossible de lire le service_account.json'); } $this->creds = json_decode($json, true); if (empty($this->creds['client_email']) || empty($this->creds['private_key'])) { throw new Exception('JSON service account invalide (client_email/private_key).'); } $cacheKey = md5(($cfg['credentials_path'] ?? '').'|'.php_uname(). '|' . phpversion()); $this->cacheFileBase = sys_get_temp_dir().'/google_token_cache_'.$cacheKey.'_'; } /** * @param string $scopes Scopes séparés par des espaces (ex: "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/documents") * @param ?string $impersonateEmail Email à impersonner (optionnel; utile pour Calendar si Domain-wide Delegation) */ public function getAccessToken(string $scopes, ?string $impersonateEmail = null): string { $cacheFile = $this->cacheFileBase . md5($scopes.'|'.($impersonateEmail ?: '')); // Cache simple (~55 min) if (is_file($cacheFile)) { $cache = json_decode(@file_get_contents($cacheFile), true); if (!empty($cache['access_token']) && time() < ($cache['expires_at'] ?? 0)) { return $cache['access_token']; } } $aud = $this->creds['token_uri'] ?? 'https://oauth2.googleapis.com/token'; $now = time(); $header = ['alg'=>'RS256','typ'=>'JWT']; $payload = [ 'iss' => $this->creds['client_email'], 'scope' => $scopes, 'aud' => $aud, 'exp' => $now + 3600, 'iat' => $now, ]; if ($impersonateEmail) { $payload['sub'] = $impersonateEmail; } $assertion = $this->b64($header).'.'.$this->b64($payload); $pkey = openssl_pkey_get_private($this->creds['private_key']); if (!$pkey) { throw new Exception('Impossible de charger la clé privée du service account.'); } if (!openssl_sign($assertion, $sig, $pkey, OPENSSL_ALGO_SHA256)) { throw new Exception('Échec openssl_sign()'); } $jwt = $assertion.'.'.$this->b64($sig); // Échange JWT -> access_token $post = http_build_query([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion' => $jwt, ]); $ch = curl_init($aud); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $post, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20, CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'], ]); $raw = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $err = curl_error($ch); curl_close($ch); if ($raw === false || $code !== 200) { throw new Exception("Token error ($code): $raw $err"); } $data = json_decode($raw, true); if (empty($data['access_token'])) { throw new Exception('Réponse token invalide: '.$raw); } // Cache ~55 min $ttl = max(300, (int)($data['expires_in'] ?? 3600) - 300); @file_put_contents($cacheFile, json_encode([ 'access_token' => $data['access_token'], 'expires_at' => time() + $ttl, ])); return $data['access_token']; } private function b64($arrOrBin): string { $bin = is_string($arrOrBin) ? $arrOrBin : json_encode($arrOrBin); return rtrim(strtr(base64_encode($bin), '+/', '-_'), '='); } }