scope = "https://www.googleapis.com/auth/calendar"; // // $I->debug = true; // // /* -------------------------------------- */ // /* dati da prendere da developer console */ // /* https://console.developers.google.com/ */ // /* applicationi autorizzate qui: */ // /* https://security.google.com/settings/security/permissions?pli=1 */ // /* -------------------------------------- */ // $I->client_id = "817077313863-vd0ed0olfg6u38ek905i71mvseqltlf3.apps.googleusercontent.com"; // $I->client_secret = "7alawTI5zjgjHnlyCqQ7Bsrz"; // $I->authorized_redirect_uri = "http://www.demo.tnx.it/carlo/google_api_oauth/"; // /* -------------------------------------- */ class GoogleApisOAuth2{ //https://developers.google.com/accounts/docs/OAuth2WebServer var $codeUrlVar = 'code';//è fissa nelle specifiche di google, non deve essere usata nelle pagine in cui si prevede possibile l'autenticazione var $client_id; var $client_secret; var $private_json_key; var $authorized_redirect_uri;//deve contenere step=oauthresponse var $scope; var $ignoraResponseStatuses = [];//response code http per cui non inviare segnalazioni email var $debug = false; var $_refreshing_access_token = false;//solo per monitorare un eventuale loop function store($id, $data){ return file_put_contents("params/".$id, $data); } function read($id){ return @file_get_contents("params/".$id); } function clear($id){ return $this->store($id, ''); } function error($type, $desc){ switch($type){ case 'access_denied': die("L'utente non ha concesso l'accesso"); break; default: die("Errore '".$type."': ".$desc); } } function getServiceAccessToken(){ //https://developers.google.com/identity/protocols/oauth2/service-account?hl=it#httprest if(!$this->serviceAccessToken){//lo cacho per chiamate consecutive (fcm ordinalo), dura un'ora $dir = dirname(__FILE__)."/../phpjwt/"; require_once($dir."JWT.php"); require_once($dir."JWK.php"); $payload = [ "iss" => $this->private_json_key->client_email,//L'indirizzo email dell'account di servizio. "scope" => $this->scope,//Un elenco di autorizzazioni delimitati da spazi richiesto dall'applicazione. "aud" => "https://oauth2.googleapis.com/token",//Un descrittore del target previsto dell'asserzione. Quando si effettua una richiesta di token di accesso, questo valore è sempre https://oauth2.googleapis.com/token. "exp" => time()+3600,//La scadenza dell'asserzione, espressa in secondi dalle 00:00:00 UTC, 1 gennaio 1970. Questo valore ha un massimo di 1 ora dopo l'orario di emissione. "iat" => time(),//L'ora in cui è stata rilasciata l'affermazione, espressa in secondi dopo le 00:00:00 UTC, 1 gennaio 1970. ]; $jwt = Firebase\JWT\JWT::encode($payload, $this->private_json_key->private_key, 'RS256'/*HS256*/); $this->serviceAccessToken = $this->request(false, 'POST', "https://oauth2.googleapis.com/token", http_build_query([ "grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion" => $jwt ])); } return $this->serviceAccessToken; } function getAccessToken(){ if($ACCESS_TOKEN = $this->read("access_token")) return $ACCESS_TOKEN; if($_GET[$this->codeUrlVar]){//$_GET['code'] contiene l'authorization_code che può essere usato una sola volta if($_GET['error']) trigger_error("Errore: ".$_GET['error'], E_USER_ERROR); else{ $response = $this->accessTokenRequest(array( 'code' => stripslashes($_GET[$this->codeUrlVar]), "redirect_uri" => $this->authorized_redirect_uri,//lo vogliono solo solo per una verifica pare 'grant_type' => 'authorization_code' )); if(!$response->error){ if($response->refresh_token) $this->store('refresh_token', $response->refresh_token); $this->store('access_token', $response->access_token); } else if($response->error != 'invalid_grant'){ $this->error($response->error, $response->error_description); } //se arriva qui ho memorizzato l'access_token oppure provo a riautorizzare (invalid_grant: Code was already redeemed.) $this->restart(); } } if($REFRESH_TOKEN = $this->read("refresh_token")){ $response = $this->accessTokenRequest(array( 'refresh_token' => $REFRESH_TOKEN, 'grant_type' => 'refresh_token' )); if(!$response->error){ $this->store('access_token', $response->access_token); return $response->access_token; } else if($response->error != 'invalid_grant'){ $this->error($response->error, $response->error_description); return; } else{ //se arriva qui provo a riautorizzare //invalid_grant: Token has been revoked. $this->clear("refresh_token"); $this->restart(); } } header("Location: https://accounts.google.com/o/oauth2/auth?".http_build_query(array( "response_type" => "code", "approval_prompt" => "force",//se l'utente ha già autorizzato l'app tornerebbe ci sarebbe subito il redirect di ritorno *** MA NON VIENE INVIATO IL REFRESH_TOKEN *** (l'app continua a funzionare facendosi riautorizzare sempre i token) "access_type" => "offline", "scope" => $this->scope, "client_id" => $this->client_id, "redirect_uri" => $this->authorized_redirect_uri, ))); die;//importante! se no l'esecuzione del codice va avanti e ci potrebbe essere un altro Location (restart) che sovrascrive questo generando un loop } function accessTokenRequest($postfields){ $postfields = array_merge( array( "client_id" => $this->client_id, 'client_secret' => $this->client_secret ), $postfields ); return $this->request(false, 'POST', "https://www.googleapis.com/oauth2/v3/token", $postfields); } function revokeTokens(){ //The token can be an access token or a refresh token. If the token is an access token and it has a corresponding refresh token, the refresh token will also be revoked. $token = $this->read("access_token"); if(!$token) $token = $this->read("refresh_token");//può darsi che access_token sia vuoto per qualche incasinamento allora revoco il refresh if($token) $return = $this->request(false, 'GET', "https://accounts.google.com/o/oauth2/revoke?token=".rawurlencode($token)); $this->clear("access_token"); $this->clear("refresh_token"); //If the revocation is successfully processed, then the status code of the response is 200. For error conditions, a status code 400 is returned along with an error code. //Note: Following a successful revocation response, it may take some time before the revocation has full effect. } function restart(){ header("Location: ".preg_replace("/(?<=[&\?])code=[^&]*/", "", "http".($_SERVER['HTTPS']=='on'?'s':'')."://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])); die(); } function get($url, $params = null){ return $this->request(true, 'GET', $url, $params); } function delete($url, $params = null){ return $this->request(true, 'DELETE', $url, $params); } function put($url, $params){ return $this->request(true, 'PUT', $url, $params); } function post($url, $postdata){ return $this->request(true, 'POST', $url, $postdata); } function request($apiCall, $method='GET', $url, $data = ''){ $args = func_get_args(); // print_r_tnx($args, $_SERVER['REMOTE_ADDR'] == '192.168.0.177'); /* include the access token in a request to the API by including either an access_token query parameter or an Authorization: Bearer HTTP header. When possible, the HTTP header is preferable, because query strings tend to be visible in server logs. */ $c = curl_init(); if($apiCall){ $header[] = "Authorization: Bearer ".($apiCall === 'service' ? $this->getServiceAccessToken()->access_token : $this->getAccessToken()); if($data && $method != 'GET'){//put, post $header[] = "Content-Type: application/json"; $data = json_encode($data); // if($apiCall == 'service') $header[] = "Content-lenght: ".strlen($data); } curl_setopt($c, CURLOPT_HTTPHEADER, $header); } if($data){ if(is_array($data)) $data = http_build_query($data); if($this->debug){ ?>
$data:
'; } if($method == 'GET'){ $url .= "?".$data; } else{//put, post curl_setopt($c, CURLOPT_POSTFIELDS, $data); } } if($this->debug){ curl_setopt($c, CURLOPT_VERBOSE, true); $debugRes = fopen("php://output", 'w+'); fwrite($debugRes, "
");
			curl_setopt($c, CURLOPT_STDERR, $debugRes);
		}
		
		curl_setopt($c, CURLOPT_URL, $url);
		curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($c, CURLOPT_CUSTOMREQUEST, $method);
		
		$response = json_decode(curl_exec($c));	
		$resCode = curl_getinfo($c, CURLINFO_HTTP_CODE);
		curl_close($c);
		
		
		if($response->error->code == 401){
			//$this->restart();
			
			if($this->_refreshing_access_token){
				//a volte succede anche se successivamente il token viene rinfrescato correttamente
				$plainText = ["http".(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 's' : '')."://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']];
				$debugBacktrace = debug_backtrace(); array_unshift($debugBacktrace, array('file'=>__FILE__, 'line'=>__LINE__, 'function'=>'debugTnx')); foreach($debugBacktrace as $debugLine) $plainText[] = $debugLine['file'].":".$debugLine['line']." ".$debugLine['function']."()";

				//8/5/25 da poldoexpress da' errore THIRD_PARTY_AUTH_ERROR (401) mandando una push a un token dell'iphone di lorenzo (strano): cancellato token
				mail("c@localhost", "Loop ".__FILE__.":".__LINE__." ?", print_r(array(
					$response, 
					$this->serviceAccessToken, 
					// $this->oldServiceAccessTokenDebug, 
					$plainText, 
					// $header, 
					$_REQUEST, 
					$_SERVER
				), true));
				
				return;
			}
			else{
				if($apiCall !== 'service') $this->clear("access_token");
				else{
					// $this->oldServiceAccessTokenDebug = $this->serviceAccessToken;
					$this->serviceAccessToken = null;
				}
				
				$this->_refreshing_access_token = true;
				$return = call_user_func_array(array($this, "request"), $args);
				$this->_refreshing_access_token = false;
				return $return;
			}
		}
		else if(!in_array($resCode, array_merge([200, 204, 503/*Backend Error*/], $this->ignoraResponseStatuses))){
			mail("c@localhost", "Fallita chiamata Google API", __FILE__.":".__LINE__."\n".print_r(array(
				"reference GCM" => "https://firebase.google.com/docs/cloud-messaging/scale-fcm?hl=it", 
				"http_code" => $resCode, 
				"response" => $response, 
				"method" => $method, 
				"url" => $url, 
				"data" => $data, 
				'$_REQUEST' => $_REQUEST, 
				'$_SERVER' => $_SERVER
			), true));
		}
		
			
		return $response;
		
	}
	
	
}






?>