PluginProbe
Media Cloud Sync / 1.4.2
Media Cloud Sync v1.4.2
1.4.2 1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 All 36 releases
← All changes | includes/sdk/google/google/auth/src/Credentials/ServiceAccountCredentials.php +79 -31 1.0.3 → 1.4.2 View file →
@@ -14,16 +14,18 @@
14 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 15 * See the License for the specific language governing permissions and
16 16 * limitations under the License.
17 17 */
18 -namespace Dudlewebs\WPMCS\Google\Auth\Credentials;
18 +namespace Dudlewebs\WPMCS\GCP\Google\Auth\Credentials;
19 19
20 -use Dudlewebs\WPMCS\Google\Auth\CredentialsLoader;
21 -use Dudlewebs\WPMCS\Google\Auth\GetQuotaProjectInterface;
22 -use Dudlewebs\WPMCS\Google\Auth\OAuth2;
23 -use Dudlewebs\WPMCS\Google\Auth\ProjectIdProviderInterface;
24 -use Dudlewebs\WPMCS\Google\Auth\ServiceAccountSignerTrait;
25 -use Dudlewebs\WPMCS\Google\Auth\SignBlobInterface;
20 +use Dudlewebs\WPMCS\GCP\Firebase\JWT\JWT;
21 +use Dudlewebs\WPMCS\GCP\Google\Auth\CredentialsLoader;
22 +use Dudlewebs\WPMCS\GCP\Google\Auth\GetQuotaProjectInterface;
23 +use Dudlewebs\WPMCS\GCP\Google\Auth\Iam;
24 +use Dudlewebs\WPMCS\GCP\Google\Auth\OAuth2;
25 +use Dudlewebs\WPMCS\GCP\Google\Auth\ProjectIdProviderInterface;
26 +use Dudlewebs\WPMCS\GCP\Google\Auth\ServiceAccountSignerTrait;
27 +use Dudlewebs\WPMCS\GCP\Google\Auth\SignBlobInterface;
26 28 use InvalidArgumentException;
27 29 /**
28 30 * ServiceAccountCredentials supports authorization using a Google service
29 31 * account.
@@ -60,8 +62,15 @@
60 62 class ServiceAccountCredentials extends CredentialsLoader implements GetQuotaProjectInterface, SignBlobInterface, ProjectIdProviderInterface
61 63 {
62 64 use ServiceAccountSignerTrait;
63 65 /**
66 + * Used in observability metric headers
67 + *
68 + * @var string
69 + */
70 + private const CRED_TYPE = 'sa';
71 + private const IAM_SCOPE = 'https://www.googleapis.com/auth/iam';
72 + /**
64 73 * The OAuth2 instance used to conduct authorization.
65 74 *
66 75 * @var OAuth2
67 76 */
@@ -92,8 +101,13 @@
92 101 * @var string
93 102 */
94 103 private string $universeDomain;
95 104 /**
105 + * Whether this is an ID token request or an access token request. Used when
106 + * building the metric header.
107 + */
108 + private bool $isIdTokenRequest = \false;
109 + /**
96 110 * Create a new ServiceAccountCredentials.
97 111 *
98 112 * @param string|string[]|null $scope the scope of the access request, expressed
99 113 * either as an Array or as a space-delimited String.
@@ -104,24 +118,24 @@
104 118 * @param string $targetAudience The audience for the ID token.
105 119 */
106 120 public function __construct($scope, $jsonKey, $sub = null, $targetAudience = null)
107 121 {
108 - if (is_string($jsonKey)) {
109 - if (!file_exists($jsonKey)) {
122 + if (\is_string($jsonKey)) {
123 + if (!\file_exists($jsonKey)) {
110 124 throw new \InvalidArgumentException('file does not exist');
111 125 }
112 - $jsonKeyStream = file_get_contents($jsonKey);
113 - if (!$jsonKey = json_decode((string) $jsonKeyStream, \true)) {
126 + $jsonKeyStream = \file_get_contents($jsonKey);
127 + if (!($jsonKey = \json_decode((string) $jsonKeyStream, \true))) {
114 128 throw new \LogicException('invalid json for auth config');
115 129 }
116 130 }
117 - if (!array_key_exists('client_email', $jsonKey)) {
131 + if (!\array_key_exists('client_email', $jsonKey)) {
118 132 throw new \InvalidArgumentException('json key is missing the client_email field');
119 133 }
120 - if (!array_key_exists('private_key', $jsonKey)) {
134 + if (!\array_key_exists('private_key', $jsonKey)) {
121 135 throw new \InvalidArgumentException('json key is missing the private_key field');
122 136 }
123 - if (array_key_exists('quota_project_id', $jsonKey)) {
137 + if (\array_key_exists('quota_project_id', $jsonKey)) {
124 138 $this->quotaProject = (string) $jsonKey['quota_project_id'];
125 139 }
126 140 if ($scope && $targetAudience) {
127 141 throw new InvalidArgumentException('Scope and targetAudience cannot both be supplied');
@@ -128,10 +142,11 @@
128 142 }
129 143 $additionalClaims = [];
130 144 if ($targetAudience) {
131 145 $additionalClaims = ['target_audience' => $targetAudience];
146 + $this->isIdTokenRequest = \true;
132 147 }
133 - $this->auth = new OAuth2(['audience' => self::TOKEN_CREDENTIAL_URI, 'issuer' => $jsonKey['client_email'], 'scope' => $scope, 'signingAlgorithm' => 'RS256', 'signingKey' => $jsonKey['private_key'], 'sub' => $sub, 'tokenCredentialUri' => self::TOKEN_CREDENTIAL_URI, 'additionalClaims' => $additionalClaims]);
148 + $this->auth = new OAuth2(['audience' => self::TOKEN_CREDENTIAL_URI, 'issuer' => $jsonKey['client_email'], 'scope' => $scope, 'signingAlgorithm' => 'RS256', 'signingKey' => $jsonKey['private_key'], 'signingKeyId' => $jsonKey['private_key_id'] ?? null, 'sub' => $sub, 'tokenCredentialUri' => self::TOKEN_CREDENTIAL_URI, 'additionalClaims' => $additionalClaims]);
134 149 $this->projectId = $jsonKey['project_id'] ?? null;
135 150 $this->universeDomain = $jsonKey['universe_domain'] ?? self::DEFAULT_UNIVERSE_DOMAIN;
136 151 }
137 152 /**
@@ -147,9 +162,11 @@
147 162 {
148 163 $this->useJwtAccessWithScope = \true;
149 164 }
150 165 /**
151 - * @param callable $httpHandler
166 + * @param callable|null $httpHandler
167 + * @param array<mixed> $headers [optional] Headers to be inserted
168 + * into the token endpoint request present.
152 169 *
153 170 * @return array<mixed> {
154 171 * A set of auth related metadata, containing the following
155 172 *
@@ -157,9 +174,9 @@
157 174 * @type int $expires_in
158 175 * @type string $token_type
159 176 * }
160 177 */
161 - public function fetchAuthToken(callable $httpHandler = null)
178 + public function fetchAuthToken(?callable $httpHandler = null, array $headers = [])
162 179 {
163 180 if ($this->useSelfSignedJwt()) {
164 181 $jwtCreds = $this->createJwtAccessCredentials();
165 182 $accessToken = $jwtCreds->fetchAuthToken($httpHandler);
@@ -168,18 +185,34 @@
168 185 $this->lastReceivedJwtAccessToken = $lastReceivedToken;
169 186 }
170 187 return $accessToken;
171 188 }
172 - return $this->auth->fetchAuthToken($httpHandler);
189 + if ($this->isIdTokenRequest && $this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) {
190 + $now = \time();
191 + $jwt = Jwt::encode(['iss' => $this->auth->getIssuer(), 'sub' => $this->auth->getIssuer(), 'scope' => self::IAM_SCOPE, 'exp' => $now + $this->auth->getExpiry(), 'iat' => $now - OAuth2::DEFAULT_SKEW_SECONDS], $this->auth->getSigningKey(), $this->auth->getSigningAlgorithm(), $this->auth->getSigningKeyId());
192 + // We create a new instance of Iam each time because the `$httpHandler` might change.
193 + $idToken = (new Iam($httpHandler, $this->getUniverseDomain()))->generateIdToken($this->auth->getIssuer(), $this->auth->getAdditionalClaims()['target_audience'], $jwt, $this->applyTokenEndpointMetrics($headers, 'it'));
194 + return ['id_token' => $idToken];
195 + }
196 + return $this->auth->fetchAuthToken($httpHandler, $this->applyTokenEndpointMetrics($headers, $this->isIdTokenRequest ? 'it' : 'at'));
173 197 }
174 198 /**
199 + * Return the Cache Key for the credentials.
200 + * For the cache key format is one of the following:
201 + * ClientEmail.Scope[.Sub]
202 + * ClientEmail.Audience[.Sub]
203 + *
175 204 * @return string
176 205 */
177 206 public function getCacheKey()
178 207 {
179 - $key = $this->auth->getIssuer() . ':' . $this->auth->getCacheKey();
208 + $scopeOrAudience = $this->auth->getScope();
209 + if (!$scopeOrAudience) {
210 + $scopeOrAudience = $this->auth->getAudience();
211 + }
212 + $key = $this->auth->getIssuer() . '.' . $scopeOrAudience;
180 213 if ($sub = $this->auth->getSub()) {
181 - $key .= ':' . $sub;
214 + $key .= '.' . $sub;
182 215 }
183 216 return $key;
184 217 }
185 218 /**
@@ -195,12 +228,12 @@
195 228 * Get the project ID from the service account keyfile.
196 229 *
197 230 * Returns null if the project ID does not exist in the keyfile.
198 231 *
199 - * @param callable $httpHandler Not used by this credentials type.
232 + * @param callable|null $httpHandler Not used by this credentials type.
200 233 * @return string|null
201 234 */
202 - public function getProjectId(callable $httpHandler = null)
235 + public function getProjectId(?callable $httpHandler = null)
203 236 {
204 237 return $this->projectId;
205 238 }
206 239 /**
@@ -207,12 +240,12 @@
207 240 * Updates metadata with the authorization token.
208 241 *
209 242 * @param array<mixed> $metadata metadata hashmap
210 243 * @param string $authUri optional auth uri
211 - * @param callable $httpHandler callback which delivers psr7 request
244 + * @param callable|null $httpHandler callback which delivers psr7 request
212 245 * @return array<mixed> updated metadata hashmap
213 246 */
214 - public function updateMetadata($metadata, $authUri = null, callable $httpHandler = null)
247 + public function updateMetadata($metadata, $authUri = null, ?callable $httpHandler = null)
215 248 {
216 249 // scope exists. use oauth implementation
217 250 if (!$this->useSelfSignedJwt()) {
218 251 return parent::updateMetadata($metadata, $authUri, $httpHandler);
@@ -255,16 +288,27 @@
255 288 * Get the client name from the keyfile.
256 289 *
257 290 * In this case, it returns the keyfile's client_email key.
258 291 *
259 - * @param callable $httpHandler Not used by this credentials type.
292 + * @param callable|null $httpHandler Not used by this credentials type.
260 293 * @return string
261 294 */
262 - public function getClientName(callable $httpHandler = null)
295 + public function getClientName(?callable $httpHandler = null)
263 296 {
264 297 return $this->auth->getIssuer();
265 298 }
266 299 /**
300 + * Get the private key from the keyfile.
301 + *
302 + * In this case, it returns the keyfile's private_key key, needed for JWT signing.
303 + *
304 + * @return string
305 + */
306 + public function getPrivateKey()
307 + {
308 + return $this->auth->getSigningKey();
309 + }
310 + /**
267 311 * Get the quota project used for this API request
268 312 *
269 313 * @return string|null
270 314 */
@@ -276,12 +320,16 @@
276 320 * Get the universe domain configured in the JSON credential.
277 321 *
278 322 * @return string
279 323 */
280 - public function getUniverseDomain(): string
324 + public function getUniverseDomain() : string
281 325 {
282 326 return $this->universeDomain;
283 327 }
328 + protected function getCredType() : string
329 + {
330 + return self::CRED_TYPE;
331 + }
284 332 /**
285 333 * @return bool
286 334 */
287 335 private function useSelfSignedJwt()
@@ -290,14 +338,14 @@
290 338 // with self-signed JWTs
291 339 if (null !== $this->auth->getSub()) {
292 340 // If we are outside the GDU, we can't use domain-wide delegation
293 341 if ($this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) {
294 - throw new \LogicException(sprintf('Service Account subject is configured for the credential. Domain-wide ' . 'delegation is not supported in universes other than %s.', self::DEFAULT_UNIVERSE_DOMAIN));
342 + throw new \LogicException(\sprintf('Service Account subject is configured for the credential. Domain-wide ' . 'delegation is not supported in universes other than %s.', self::DEFAULT_UNIVERSE_DOMAIN));
295 343 }
296 344 return \false;
297 345 }
298 - // If claims are set, this call is for "id_tokens"
299 - if ($this->auth->getAdditionalClaims()) {
346 + // Do not use self-signed JWT for ID tokens
347 + if ($this->isIdTokenRequest) {
300 348 return \false;
301 349 }
302 350 // When true, ServiceAccountCredentials will always use JwtAccess for access tokens
303 351 if ($this->useJwtAccessWithScope) {
@@ -306,7 +354,7 @@
306 354 // If the universe domain is outside the GDU, use JwtAccess for access tokens
307 355 if ($this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) {
308 356 return \true;
309 357 }
310 - return is_null($this->auth->getScope());
358 + return \is_null($this->auth->getScope());
311 359 }
312 360 }