1<?php 2/* 3 * Copyright 2015 Google Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18namespace Google\Auth\Credentials; 19 20/* 21 * The AppIdentityService class is automatically defined on App Engine, 22 * so including this dependency is not necessary, and will result in a 23 * PHP fatal error in the App Engine environment. 24 */ 25use google\appengine\api\app_identity\AppIdentityService; 26use Google\Auth\CredentialsLoader; 27use Google\Auth\ProjectIdProviderInterface; 28use Google\Auth\SignBlobInterface; 29 30/** 31 * @deprecated 32 * 33 * AppIdentityCredentials supports authorization on Google App Engine. 34 * 35 * It can be used to authorize requests using the AuthTokenMiddleware or 36 * AuthTokenSubscriber, but will only succeed if being run on App Engine: 37 * 38 * Example: 39 * ``` 40 * use Google\Auth\Credentials\AppIdentityCredentials; 41 * use Google\Auth\Middleware\AuthTokenMiddleware; 42 * use GuzzleHttp\Client; 43 * use GuzzleHttp\HandlerStack; 44 * 45 * $gae = new AppIdentityCredentials('https://www.googleapis.com/auth/books'); 46 * $middleware = new AuthTokenMiddleware($gae); 47 * $stack = HandlerStack::create(); 48 * $stack->push($middleware); 49 * 50 * $client = new Client([ 51 * 'handler' => $stack, 52 * 'base_uri' => 'https://www.googleapis.com/books/v1', 53 * 'auth' => 'google_auth' 54 * ]); 55 * 56 * $res = $client->get('volumes?q=Henry+David+Thoreau&country=US'); 57 * ``` 58 */ 59class AppIdentityCredentials extends CredentialsLoader implements 60 SignBlobInterface, 61 ProjectIdProviderInterface 62{ 63 /** 64 * Result of fetchAuthToken. 65 * 66 * @var array<mixed> 67 */ 68 protected $lastReceivedToken; 69 70 /** 71 * Array of OAuth2 scopes to be requested. 72 * 73 * @var string[] 74 */ 75 private $scope; 76 77 /** 78 * @var string 79 */ 80 private $clientName; 81 82 /** 83 * @param string|string[] $scope One or more scopes. 84 */ 85 public function __construct($scope = []) 86 { 87 $this->scope = is_array($scope) ? $scope : explode(' ', (string) $scope); 88 } 89 90 /** 91 * Determines if this an App Engine instance, by accessing the 92 * SERVER_SOFTWARE environment variable (prod) or the APPENGINE_RUNTIME 93 * environment variable (dev). 94 * 95 * @return bool true if this an App Engine Instance, false otherwise 96 */ 97 public static function onAppEngine() 98 { 99 $appEngineProduction = isset($_SERVER['SERVER_SOFTWARE']) && 100 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine'); 101 if ($appEngineProduction) { 102 return true; 103 } 104 $appEngineDevAppServer = isset($_SERVER['APPENGINE_RUNTIME']) && 105 $_SERVER['APPENGINE_RUNTIME'] == 'php'; 106 if ($appEngineDevAppServer) { 107 return true; 108 } 109 return false; 110 } 111 112 /** 113 * Implements FetchAuthTokenInterface#fetchAuthToken. 114 * 115 * Fetches the auth tokens using the AppIdentityService if available. 116 * As the AppIdentityService uses protobufs to fetch the access token, 117 * the GuzzleHttp\ClientInterface instance passed in will not be used. 118 * 119 * @param callable $httpHandler callback which delivers psr7 request 120 * @return array<mixed> { 121 * A set of auth related metadata, containing the following 122 * 123 * @type string $access_token 124 * @type string $expiration_time 125 * } 126 */ 127 public function fetchAuthToken(callable $httpHandler = null) 128 { 129 try { 130 $this->checkAppEngineContext(); 131 } catch (\Exception $e) { 132 return []; 133 } 134 135 /** @phpstan-ignore-next-line */ 136 $token = AppIdentityService::getAccessToken($this->scope); 137 $this->lastReceivedToken = $token; 138 139 return $token; 140 } 141 142 /** 143 * Sign a string using AppIdentityService. 144 * 145 * @param string $stringToSign The string to sign. 146 * @param bool $forceOpenSsl [optional] Does not apply to this credentials 147 * type. 148 * @return string The signature, base64-encoded. 149 * @throws \Exception If AppEngine SDK or mock is not available. 150 */ 151 public function signBlob($stringToSign, $forceOpenSsl = false) 152 { 153 $this->checkAppEngineContext(); 154 155 /** @phpstan-ignore-next-line */ 156 return base64_encode(AppIdentityService::signForApp($stringToSign)['signature']); 157 } 158 159 /** 160 * Get the project ID from AppIdentityService. 161 * 162 * Returns null if AppIdentityService is unavailable. 163 * 164 * @param callable $httpHandler Not used by this type. 165 * @return string|null 166 */ 167 public function getProjectId(callable $httpHandler = null) 168 { 169 try { 170 $this->checkAppEngineContext(); 171 } catch (\Exception $e) { 172 return null; 173 } 174 175 /** @phpstan-ignore-next-line */ 176 return AppIdentityService::getApplicationId(); 177 } 178 179 /** 180 * Get the client name from AppIdentityService. 181 * 182 * Subsequent calls to this method will return a cached value. 183 * 184 * @param callable $httpHandler Not used in this implementation. 185 * @return string 186 * @throws \Exception If AppEngine SDK or mock is not available. 187 */ 188 public function getClientName(callable $httpHandler = null) 189 { 190 $this->checkAppEngineContext(); 191 192 if (!$this->clientName) { 193 /** @phpstan-ignore-next-line */ 194 $this->clientName = AppIdentityService::getServiceAccountName(); 195 } 196 197 return $this->clientName; 198 } 199 200 /** 201 * @return array{access_token:string,expires_at:int}|null 202 */ 203 public function getLastReceivedToken() 204 { 205 if ($this->lastReceivedToken) { 206 return [ 207 'access_token' => $this->lastReceivedToken['access_token'], 208 'expires_at' => $this->lastReceivedToken['expiration_time'], 209 ]; 210 } 211 212 return null; 213 } 214 215 /** 216 * Caching is handled by the underlying AppIdentityService, return empty string 217 * to prevent caching. 218 * 219 * @return string 220 */ 221 public function getCacheKey() 222 { 223 return ''; 224 } 225 226 /** 227 * @return void 228 */ 229 private function checkAppEngineContext() 230 { 231 if (!self::onAppEngine() || !class_exists('google\appengine\api\app_identity\AppIdentityService')) { 232 throw new \Exception( 233 'This class must be run in App Engine, or you must include the AppIdentityService ' 234 . 'mock class defined in tests/mocks/AppIdentityService.php' 235 ); 236 } 237 } 238} 239