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; 19 20use DomainException; 21use Google\Auth\Credentials\AppIdentityCredentials; 22use Google\Auth\Credentials\GCECredentials; 23use Google\Auth\Credentials\ServiceAccountCredentials; 24use Google\Auth\HttpHandler\HttpClientCache; 25use Google\Auth\HttpHandler\HttpHandlerFactory; 26use Google\Auth\Middleware\AuthTokenMiddleware; 27use Google\Auth\Middleware\ProxyAuthTokenMiddleware; 28use Google\Auth\Subscriber\AuthTokenSubscriber; 29use GuzzleHttp\Client; 30use InvalidArgumentException; 31use Psr\Cache\CacheItemPoolInterface; 32 33/** 34 * ApplicationDefaultCredentials obtains the default credentials for 35 * authorizing a request to a Google service. 36 * 37 * Application Default Credentials are described here: 38 * https://developers.google.com/accounts/docs/application-default-credentials 39 * 40 * This class implements the search for the application default credentials as 41 * described in the link. 42 * 43 * It provides three factory methods: 44 * - #get returns the computed credentials object 45 * - #getSubscriber returns an AuthTokenSubscriber built from the credentials object 46 * - #getMiddleware returns an AuthTokenMiddleware built from the credentials object 47 * 48 * This allows it to be used as follows with GuzzleHttp\Client: 49 * 50 * ``` 51 * use Google\Auth\ApplicationDefaultCredentials; 52 * use GuzzleHttp\Client; 53 * use GuzzleHttp\HandlerStack; 54 * 55 * $middleware = ApplicationDefaultCredentials::getMiddleware( 56 * 'https://www.googleapis.com/auth/taskqueue' 57 * ); 58 * $stack = HandlerStack::create(); 59 * $stack->push($middleware); 60 * 61 * $client = new Client([ 62 * 'handler' => $stack, 63 * 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/', 64 * 'auth' => 'google_auth' // authorize all requests 65 * ]); 66 * 67 * $res = $client->get('myproject/taskqueues/myqueue'); 68 * ``` 69 */ 70class ApplicationDefaultCredentials 71{ 72 /** 73 * @deprecated 74 * 75 * Obtains an AuthTokenSubscriber that uses the default FetchAuthTokenInterface 76 * implementation to use in this environment. 77 * 78 * If supplied, $scope is used to in creating the credentials instance if 79 * this does not fallback to the compute engine defaults. 80 * 81 * @param string|string[] $scope the scope of the access request, expressed 82 * either as an Array or as a space-delimited String. 83 * @param callable $httpHandler callback which delivers psr7 request 84 * @param array<mixed> $cacheConfig configuration for the cache when it's present 85 * @param CacheItemPoolInterface $cache A cache implementation, may be 86 * provided if you have one already available for use. 87 * @return AuthTokenSubscriber 88 * @throws DomainException if no implementation can be obtained. 89 */ 90 public static function getSubscriber(// @phpstan-ignore-line 91 $scope = null, 92 callable $httpHandler = null, 93 array $cacheConfig = null, 94 CacheItemPoolInterface $cache = null 95 ) { 96 $creds = self::getCredentials($scope, $httpHandler, $cacheConfig, $cache); 97 98 /** @phpstan-ignore-next-line */ 99 return new AuthTokenSubscriber($creds, $httpHandler); 100 } 101 102 /** 103 * Obtains an AuthTokenMiddleware that uses the default FetchAuthTokenInterface 104 * implementation to use in this environment. 105 * 106 * If supplied, $scope is used to in creating the credentials instance if 107 * this does not fallback to the compute engine defaults. 108 * 109 * @param string|string[] $scope the scope of the access request, expressed 110 * either as an Array or as a space-delimited String. 111 * @param callable $httpHandler callback which delivers psr7 request 112 * @param array<mixed> $cacheConfig configuration for the cache when it's present 113 * @param CacheItemPoolInterface $cache A cache implementation, may be 114 * provided if you have one already available for use. 115 * @param string $quotaProject specifies a project to bill for access 116 * charges associated with the request. 117 * @return AuthTokenMiddleware 118 * @throws DomainException if no implementation can be obtained. 119 */ 120 public static function getMiddleware( 121 $scope = null, 122 callable $httpHandler = null, 123 array $cacheConfig = null, 124 CacheItemPoolInterface $cache = null, 125 $quotaProject = null 126 ) { 127 $creds = self::getCredentials($scope, $httpHandler, $cacheConfig, $cache, $quotaProject); 128 129 return new AuthTokenMiddleware($creds, $httpHandler); 130 } 131 132 /** 133 * Obtains the default FetchAuthTokenInterface implementation to use 134 * in this environment. 135 * 136 * @param string|string[] $scope the scope of the access request, expressed 137 * either as an Array or as a space-delimited String. 138 * @param callable $httpHandler callback which delivers psr7 request 139 * @param array<mixed> $cacheConfig configuration for the cache when it's present 140 * @param CacheItemPoolInterface $cache A cache implementation, may be 141 * provided if you have one already available for use. 142 * @param string $quotaProject specifies a project to bill for access 143 * charges associated with the request. 144 * @param string|string[] $defaultScope The default scope to use if no 145 * user-defined scopes exist, expressed either as an Array or as a 146 * space-delimited string. 147 * 148 * @return FetchAuthTokenInterface 149 * @throws DomainException if no implementation can be obtained. 150 */ 151 public static function getCredentials( 152 $scope = null, 153 callable $httpHandler = null, 154 array $cacheConfig = null, 155 CacheItemPoolInterface $cache = null, 156 $quotaProject = null, 157 $defaultScope = null 158 ) { 159 $creds = null; 160 $jsonKey = CredentialsLoader::fromEnv() 161 ?: CredentialsLoader::fromWellKnownFile(); 162 $anyScope = $scope ?: $defaultScope; 163 164 if (!$httpHandler) { 165 if (!($client = HttpClientCache::getHttpClient())) { 166 $client = new Client(); 167 HttpClientCache::setHttpClient($client); 168 } 169 170 $httpHandler = HttpHandlerFactory::build($client); 171 } 172 173 if (!is_null($jsonKey)) { 174 if ($quotaProject) { 175 $jsonKey['quota_project_id'] = $quotaProject; 176 } 177 $creds = CredentialsLoader::makeCredentials( 178 $scope, 179 $jsonKey, 180 $defaultScope 181 ); 182 } elseif (AppIdentityCredentials::onAppEngine() && !GCECredentials::onAppEngineFlexible()) { 183 $creds = new AppIdentityCredentials($anyScope); 184 } elseif (self::onGce($httpHandler, $cacheConfig, $cache)) { 185 $creds = new GCECredentials(null, $anyScope, null, $quotaProject); 186 } 187 188 if (is_null($creds)) { 189 throw new DomainException(self::notFound()); 190 } 191 if (!is_null($cache)) { 192 $creds = new FetchAuthTokenCache($creds, $cacheConfig, $cache); 193 } 194 return $creds; 195 } 196 197 /** 198 * Obtains an AuthTokenMiddleware which will fetch an ID token to use in the 199 * Authorization header. The middleware is configured with the default 200 * FetchAuthTokenInterface implementation to use in this environment. 201 * 202 * If supplied, $targetAudience is used to set the "aud" on the resulting 203 * ID token. 204 * 205 * @param string $targetAudience The audience for the ID token. 206 * @param callable $httpHandler callback which delivers psr7 request 207 * @param array<mixed> $cacheConfig configuration for the cache when it's present 208 * @param CacheItemPoolInterface $cache A cache implementation, may be 209 * provided if you have one already available for use. 210 * @return AuthTokenMiddleware 211 * @throws DomainException if no implementation can be obtained. 212 */ 213 public static function getIdTokenMiddleware( 214 $targetAudience, 215 callable $httpHandler = null, 216 array $cacheConfig = null, 217 CacheItemPoolInterface $cache = null 218 ) { 219 $creds = self::getIdTokenCredentials($targetAudience, $httpHandler, $cacheConfig, $cache); 220 221 return new AuthTokenMiddleware($creds, $httpHandler); 222 } 223 224 /** 225 * Obtains an ProxyAuthTokenMiddleware which will fetch an ID token to use in the 226 * Authorization header. The middleware is configured with the default 227 * FetchAuthTokenInterface implementation to use in this environment. 228 * 229 * If supplied, $targetAudience is used to set the "aud" on the resulting 230 * ID token. 231 * 232 * @param string $targetAudience The audience for the ID token. 233 * @param callable $httpHandler callback which delivers psr7 request 234 * @param array<mixed> $cacheConfig configuration for the cache when it's present 235 * @param CacheItemPoolInterface $cache A cache implementation, may be 236 * provided if you have one already available for use. 237 * @return ProxyAuthTokenMiddleware 238 * @throws DomainException if no implementation can be obtained. 239 */ 240 public static function getProxyIdTokenMiddleware( 241 $targetAudience, 242 callable $httpHandler = null, 243 array $cacheConfig = null, 244 CacheItemPoolInterface $cache = null 245 ) { 246 $creds = self::getIdTokenCredentials($targetAudience, $httpHandler, $cacheConfig, $cache); 247 248 return new ProxyAuthTokenMiddleware($creds, $httpHandler); 249 } 250 251 /** 252 * Obtains the default FetchAuthTokenInterface implementation to use 253 * in this environment, configured with a $targetAudience for fetching an ID 254 * token. 255 * 256 * @param string $targetAudience The audience for the ID token. 257 * @param callable $httpHandler callback which delivers psr7 request 258 * @param array<mixed> $cacheConfig configuration for the cache when it's present 259 * @param CacheItemPoolInterface $cache A cache implementation, may be 260 * provided if you have one already available for use. 261 * @return FetchAuthTokenInterface 262 * @throws DomainException if no implementation can be obtained. 263 * @throws InvalidArgumentException if JSON "type" key is invalid 264 */ 265 public static function getIdTokenCredentials( 266 $targetAudience, 267 callable $httpHandler = null, 268 array $cacheConfig = null, 269 CacheItemPoolInterface $cache = null 270 ) { 271 $creds = null; 272 $jsonKey = CredentialsLoader::fromEnv() 273 ?: CredentialsLoader::fromWellKnownFile(); 274 275 if (!$httpHandler) { 276 if (!($client = HttpClientCache::getHttpClient())) { 277 $client = new Client(); 278 HttpClientCache::setHttpClient($client); 279 } 280 281 $httpHandler = HttpHandlerFactory::build($client); 282 } 283 284 if (!is_null($jsonKey)) { 285 if (!array_key_exists('type', $jsonKey)) { 286 throw new \InvalidArgumentException('json key is missing the type field'); 287 } 288 289 if ($jsonKey['type'] == 'authorized_user') { 290 throw new InvalidArgumentException('ID tokens are not supported for end user credentials'); 291 } 292 293 if ($jsonKey['type'] != 'service_account') { 294 throw new InvalidArgumentException('invalid value in the type field'); 295 } 296 297 $creds = new ServiceAccountCredentials(null, $jsonKey, null, $targetAudience); 298 } elseif (self::onGce($httpHandler, $cacheConfig, $cache)) { 299 $creds = new GCECredentials(null, null, $targetAudience); 300 } 301 302 if (is_null($creds)) { 303 throw new DomainException(self::notFound()); 304 } 305 if (!is_null($cache)) { 306 $creds = new FetchAuthTokenCache($creds, $cacheConfig, $cache); 307 } 308 return $creds; 309 } 310 311 /** 312 * @return string 313 */ 314 private static function notFound() 315 { 316 $msg = 'Could not load the default credentials. Browse to '; 317 $msg .= 'https://developers.google.com'; 318 $msg .= '/accounts/docs/application-default-credentials'; 319 $msg .= ' for more information'; 320 321 return $msg; 322 } 323 324 /** 325 * @param callable $httpHandler 326 * @param array<mixed> $cacheConfig 327 * @param CacheItemPoolInterface $cache 328 * @return bool 329 */ 330 private static function onGce( 331 callable $httpHandler = null, 332 array $cacheConfig = null, 333 CacheItemPoolInterface $cache = null 334 ) { 335 $gceCacheConfig = []; 336 foreach (['lifetime', 'prefix'] as $key) { 337 if (isset($cacheConfig['gce_' . $key])) { 338 $gceCacheConfig[$key] = $cacheConfig['gce_' . $key]; 339 } 340 } 341 342 return (new GCECache($gceCacheConfig, $cache))->onGce($httpHandler); 343 } 344} 345