PluginProbe
Media Cloud Sync / 1.2.12
Media Cloud Sync v1.2.12
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
media-cloud-sync / includes / sdk / google / google / auth / src / Credentials / ExternalAccountCredentials.php

ExternalAccountCredentials.php in Media Cloud Sync 1.2.12, at includes/sdk/google/google/auth/src/Credentials/ExternalAccountCredentials.php

230 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * Copyright 2023 Google Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 namespace Dudlewebs\WPMCS\Google\Auth\Credentials;
19
20 use Dudlewebs\WPMCS\Google\Auth\CredentialSource\AwsNativeSource;
21 use Dudlewebs\WPMCS\Google\Auth\CredentialSource\FileSource;
22 use Dudlewebs\WPMCS\Google\Auth\CredentialSource\UrlSource;
23 use Dudlewebs\WPMCS\Google\Auth\ExternalAccountCredentialSourceInterface;
24 use Dudlewebs\WPMCS\Google\Auth\FetchAuthTokenInterface;
25 use Dudlewebs\WPMCS\Google\Auth\GetQuotaProjectInterface;
26 use Dudlewebs\WPMCS\Google\Auth\GetUniverseDomainInterface;
27 use Dudlewebs\WPMCS\Google\Auth\HttpHandler\HttpClientCache;
28 use Dudlewebs\WPMCS\Google\Auth\HttpHandler\HttpHandlerFactory;
29 use Dudlewebs\WPMCS\Google\Auth\OAuth2;
30 use Dudlewebs\WPMCS\Google\Auth\ProjectIdProviderInterface;
31 use Dudlewebs\WPMCS\Google\Auth\UpdateMetadataInterface;
32 use Dudlewebs\WPMCS\Google\Auth\UpdateMetadataTrait;
33 use Dudlewebs\WPMCS\GuzzleHttp\Psr7\Request;
34 use InvalidArgumentException;
35 class ExternalAccountCredentials implements FetchAuthTokenInterface, UpdateMetadataInterface, GetQuotaProjectInterface, GetUniverseDomainInterface, ProjectIdProviderInterface
36 {
37 use UpdateMetadataTrait;
38 private const EXTERNAL_ACCOUNT_TYPE = 'external_account';
39 private const CLOUD_RESOURCE_MANAGER_URL = 'https://cloudresourcemanager.UNIVERSE_DOMAIN/v1/projects/%s';
40 private OAuth2 $auth;
41 private ?string $quotaProject;
42 private ?string $serviceAccountImpersonationUrl;
43 private ?string $workforcePoolUserProject;
44 private ?string $projectId;
45 private string $universeDomain;
46 /**
47 * @param string|string[] $scope The scope of the access request, expressed either as an array
48 * or as a space-delimited string.
49 * @param array<mixed> $jsonKey JSON credentials as an associative array.
50 */
51 public function __construct($scope, array $jsonKey)
52 {
53 if (!array_key_exists('type', $jsonKey)) {
54 throw new InvalidArgumentException('json key is missing the type field');
55 }
56 if ($jsonKey['type'] !== self::EXTERNAL_ACCOUNT_TYPE) {
57 throw new InvalidArgumentException(sprintf('expected "%s" type but received "%s"', self::EXTERNAL_ACCOUNT_TYPE, $jsonKey['type']));
58 }
59 if (!array_key_exists('token_url', $jsonKey)) {
60 throw new InvalidArgumentException('json key is missing the token_url field');
61 }
62 if (!array_key_exists('audience', $jsonKey)) {
63 throw new InvalidArgumentException('json key is missing the audience field');
64 }
65 if (!array_key_exists('subject_token_type', $jsonKey)) {
66 throw new InvalidArgumentException('json key is missing the subject_token_type field');
67 }
68 if (!array_key_exists('credential_source', $jsonKey)) {
69 throw new InvalidArgumentException('json key is missing the credential_source field');
70 }
71 if (array_key_exists('service_account_impersonation_url', $jsonKey)) {
72 $this->serviceAccountImpersonationUrl = $jsonKey['service_account_impersonation_url'];
73 }
74 $this->quotaProject = $jsonKey['quota_project_id'] ?? null;
75 $this->workforcePoolUserProject = $jsonKey['workforce_pool_user_project'] ?? null;
76 $this->universeDomain = $jsonKey['universe_domain'] ?? GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
77 $this->auth = new OAuth2(['tokenCredentialUri' => $jsonKey['token_url'], 'audience' => $jsonKey['audience'], 'scope' => $scope, 'subjectTokenType' => $jsonKey['subject_token_type'], 'subjectTokenFetcher' => self::buildCredentialSource($jsonKey), 'additionalOptions' => $this->workforcePoolUserProject ? ['userProject' => $this->workforcePoolUserProject] : []]);
78 if (!$this->isWorkforcePool() && $this->workforcePoolUserProject) {
79 throw new InvalidArgumentException('workforce_pool_user_project should not be set for non-workforce pool credentials.');
80 }
81 }
82 /**
83 * @param array<mixed> $jsonKey
84 */
85 private static function buildCredentialSource(array $jsonKey): ExternalAccountCredentialSourceInterface
86 {
87 $credentialSource = $jsonKey['credential_source'];
88 if (isset($credentialSource['file'])) {
89 return new FileSource($credentialSource['file'], $credentialSource['format']['type'] ?? null, $credentialSource['format']['subject_token_field_name'] ?? null);
90 }
91 if (isset($credentialSource['environment_id']) && 1 === preg_match('/^aws(\d+)$/', $credentialSource['environment_id'], $matches)) {
92 if ($matches[1] !== '1') {
93 throw new InvalidArgumentException("aws version \"{$matches[1]}\" is not supported in the current build.");
94 }
95 if (!array_key_exists('regional_cred_verification_url', $credentialSource)) {
96 throw new InvalidArgumentException('The regional_cred_verification_url field is required for aws1 credential source.');
97 }
98 if (!array_key_exists('audience', $jsonKey)) {
99 throw new InvalidArgumentException('aws1 credential source requires an audience to be set in the JSON file.');
100 }
101 return new AwsNativeSource(
102 $jsonKey['audience'],
103 $credentialSource['regional_cred_verification_url'],
104 // $regionalCredVerificationUrl
105 $credentialSource['region_url'] ?? null,
106 // $regionUrl
107 $credentialSource['url'] ?? null,
108 // $securityCredentialsUrl
109 $credentialSource['imdsv2_session_token_url'] ?? null
110 );
111 }
112 if (isset($credentialSource['url'])) {
113 return new UrlSource($credentialSource['url'], $credentialSource['format']['type'] ?? null, $credentialSource['format']['subject_token_field_name'] ?? null, $credentialSource['headers'] ?? null);
114 }
115 throw new InvalidArgumentException('Unable to determine credential source from json key.');
116 }
117 /**
118 * @param string $stsToken
119 * @param callable $httpHandler
120 *
121 * @return array<mixed> {
122 * A set of auth related metadata, containing the following
123 *
124 * @type string $access_token
125 * @type int $expires_at
126 * }
127 */
128 private function getImpersonatedAccessToken(string $stsToken, callable $httpHandler = null): array
129 {
130 if (!isset($this->serviceAccountImpersonationUrl)) {
131 throw new InvalidArgumentException('service_account_impersonation_url must be set in JSON credentials.');
132 }
133 $request = new Request('POST', $this->serviceAccountImpersonationUrl, ['Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $stsToken], (string) json_encode(['lifetime' => sprintf('%ss', OAuth2::DEFAULT_EXPIRY_SECONDS), 'scope' => explode(' ', $this->auth->getScope())]));
134 if (is_null($httpHandler)) {
135 $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
136 }
137 $response = $httpHandler($request);
138 $body = json_decode((string) $response->getBody(), \true);
139 return ['access_token' => $body['accessToken'], 'expires_at' => strtotime($body['expireTime'])];
140 }
141 /**
142 * @param callable $httpHandler
143 *
144 * @return array<mixed> {
145 * A set of auth related metadata, containing the following
146 *
147 * @type string $access_token
148 * @type int $expires_at (impersonated service accounts only)
149 * @type int $expires_in (identity pool only)
150 * @type string $issued_token_type (identity pool only)
151 * @type string $token_type (identity pool only)
152 * }
153 */
154 public function fetchAuthToken(callable $httpHandler = null)
155 {
156 $stsToken = $this->auth->fetchAuthToken($httpHandler);
157 if (isset($this->serviceAccountImpersonationUrl)) {
158 return $this->getImpersonatedAccessToken($stsToken['access_token'], $httpHandler);
159 }
160 return $stsToken;
161 }
162 public function getCacheKey()
163 {
164 return $this->auth->getCacheKey();
165 }
166 public function getLastReceivedToken()
167 {
168 return $this->auth->getLastReceivedToken();
169 }
170 /**
171 * Get the quota project used for this API request
172 *
173 * @return string|null
174 */
175 public function getQuotaProject()
176 {
177 return $this->quotaProject;
178 }
179 /**
180 * Get the universe domain used for this API request
181 *
182 * @return string
183 */
184 public function getUniverseDomain(): string
185 {
186 return $this->universeDomain;
187 }
188 /**
189 * Get the project ID.
190 *
191 * @param callable $httpHandler Callback which delivers psr7 request
192 * @param string $accessToken The access token to use to sign the blob. If
193 * provided, saves a call to the metadata server for a new access
194 * token. **Defaults to** `null`.
195 * @return string|null
196 */
197 public function getProjectId(callable $httpHandler = null, string $accessToken = null)
198 {
199 if (isset($this->projectId)) {
200 return $this->projectId;
201 }
202 $projectNumber = $this->getProjectNumber() ?: $this->workforcePoolUserProject;
203 if (!$projectNumber) {
204 return null;
205 }
206 if (is_null($httpHandler)) {
207 $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
208 }
209 $url = str_replace('UNIVERSE_DOMAIN', $this->getUniverseDomain(), sprintf(self::CLOUD_RESOURCE_MANAGER_URL, $projectNumber));
210 if (is_null($accessToken)) {
211 $accessToken = $this->fetchAuthToken($httpHandler)['access_token'];
212 }
213 $request = new Request('GET', $url, ['authorization' => 'Bearer ' . $accessToken]);
214 $response = $httpHandler($request);
215 $body = json_decode((string) $response->getBody(), \true);
216 return $this->projectId = $body['projectId'];
217 }
218 private function getProjectNumber(): ?string
219 {
220 $parts = explode('/', $this->auth->getAudience());
221 $i = array_search('projects', $parts);
222 return $parts[$i + 1] ?? null;
223 }
224 private function isWorkforcePool(): bool
225 {
226 $regex = '#//iam\.googleapis\.com/locations/[^/]+/workforcePools/#';
227 return preg_match($regex, $this->auth->getAudience()) === 1;
228 }
229 }
230