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 20use Google\Auth\CredentialsLoader; 21use Google\Auth\GetQuotaProjectInterface; 22use Google\Auth\OAuth2; 23use Google\Auth\ProjectIdProviderInterface; 24use Google\Auth\ServiceAccountSignerTrait; 25use Google\Auth\SignBlobInterface; 26 27/** 28 * Authenticates requests using Google's Service Account credentials via 29 * JWT Access. 30 * 31 * This class allows authorizing requests for service accounts directly 32 * from credentials from a json key file downloaded from the developer 33 * console (via 'Generate new Json Key'). It is not part of any OAuth2 34 * flow, rather it creates a JWT and sends that as a credential. 35 */ 36class ServiceAccountJwtAccessCredentials extends CredentialsLoader implements 37 GetQuotaProjectInterface, 38 SignBlobInterface, 39 ProjectIdProviderInterface 40{ 41 use ServiceAccountSignerTrait; 42 43 /** 44 * The OAuth2 instance used to conduct authorization. 45 * 46 * @var OAuth2 47 */ 48 protected $auth; 49 50 /** 51 * The quota project associated with the JSON credentials 52 * 53 * @var string 54 */ 55 protected $quotaProject; 56 57 /** 58 * @var string 59 */ 60 public $projectId; 61 62 /** 63 * Create a new ServiceAccountJwtAccessCredentials. 64 * 65 * @param string|array<mixed> $jsonKey JSON credential file path or JSON credentials 66 * as an associative array 67 * @param string|string[] $scope the scope of the access request, expressed 68 * either as an Array or as a space-delimited String. 69 */ 70 public function __construct($jsonKey, $scope = null) 71 { 72 if (is_string($jsonKey)) { 73 if (!file_exists($jsonKey)) { 74 throw new \InvalidArgumentException('file does not exist'); 75 } 76 $jsonKeyStream = file_get_contents($jsonKey); 77 if (!$jsonKey = json_decode((string) $jsonKeyStream, true)) { 78 throw new \LogicException('invalid json for auth config'); 79 } 80 } 81 if (!array_key_exists('client_email', $jsonKey)) { 82 throw new \InvalidArgumentException( 83 'json key is missing the client_email field' 84 ); 85 } 86 if (!array_key_exists('private_key', $jsonKey)) { 87 throw new \InvalidArgumentException( 88 'json key is missing the private_key field' 89 ); 90 } 91 if (array_key_exists('quota_project_id', $jsonKey)) { 92 $this->quotaProject = (string) $jsonKey['quota_project_id']; 93 } 94 $this->auth = new OAuth2([ 95 'issuer' => $jsonKey['client_email'], 96 'sub' => $jsonKey['client_email'], 97 'signingAlgorithm' => 'RS256', 98 'signingKey' => $jsonKey['private_key'], 99 'scope' => $scope, 100 ]); 101 102 $this->projectId = isset($jsonKey['project_id']) 103 ? $jsonKey['project_id'] 104 : null; 105 } 106 107 /** 108 * Updates metadata with the authorization token. 109 * 110 * @param array<mixed> $metadata metadata hashmap 111 * @param string $authUri optional auth uri 112 * @param callable $httpHandler callback which delivers psr7 request 113 * @return array<mixed> updated metadata hashmap 114 */ 115 public function updateMetadata( 116 $metadata, 117 $authUri = null, 118 callable $httpHandler = null 119 ) { 120 $scope = $this->auth->getScope(); 121 if (empty($authUri) && empty($scope)) { 122 return $metadata; 123 } 124 125 $this->auth->setAudience($authUri); 126 127 return parent::updateMetadata($metadata, $authUri, $httpHandler); 128 } 129 130 /** 131 * Implements FetchAuthTokenInterface#fetchAuthToken. 132 * 133 * @param callable $httpHandler 134 * 135 * @return null|array{access_token:string} A set of auth related metadata 136 */ 137 public function fetchAuthToken(callable $httpHandler = null) 138 { 139 $audience = $this->auth->getAudience(); 140 $scope = $this->auth->getScope(); 141 if (empty($audience) && empty($scope)) { 142 return null; 143 } 144 145 if (!empty($audience) && !empty($scope)) { 146 throw new \UnexpectedValueException( 147 'Cannot sign both audience and scope in JwtAccess' 148 ); 149 } 150 151 $access_token = $this->auth->toJwt(); 152 153 // Set the self-signed access token in OAuth2 for getLastReceivedToken 154 $this->auth->setAccessToken($access_token); 155 156 return ['access_token' => $access_token]; 157 } 158 159 /** 160 * @return string 161 */ 162 public function getCacheKey() 163 { 164 return $this->auth->getCacheKey(); 165 } 166 167 /** 168 * @return array<mixed> 169 */ 170 public function getLastReceivedToken() 171 { 172 return $this->auth->getLastReceivedToken(); 173 } 174 175 /** 176 * Get the project ID from the service account keyfile. 177 * 178 * Returns null if the project ID does not exist in the keyfile. 179 * 180 * @param callable $httpHandler Not used by this credentials type. 181 * @return string|null 182 */ 183 public function getProjectId(callable $httpHandler = null) 184 { 185 return $this->projectId; 186 } 187 188 /** 189 * Get the client name from the keyfile. 190 * 191 * In this case, it returns the keyfile's client_email key. 192 * 193 * @param callable $httpHandler Not used by this credentials type. 194 * @return string 195 */ 196 public function getClientName(callable $httpHandler = null) 197 { 198 return $this->auth->getIssuer(); 199 } 200 201 /** 202 * Get the quota project used for this API request 203 * 204 * @return string|null 205 */ 206 public function getQuotaProject() 207 { 208 return $this->quotaProject; 209 } 210} 211