PluginProbe
Gmail SMTP / trunk
Gmail SMTP vtrunk
1.2.3.21 1.2.3.20 trunk 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.9 1.2.0 1.2.3.14 1.2.3.15 1.2.3.16 1.2.3.18 1.2.3.5
gmail-smtp / google-api-php-client / vendor / google / auth / src / Credentials / ExternalAccountAuthorizedUserCredentials.php

ExternalAccountAuthorizedUserCredentials.php in Gmail SMTP trunk, at google-api-php-client/vendor/google/auth/src/Credentials/ExternalAccountAuthorizedUserCredentials.php

249 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * Copyright 2026 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
19 namespace Google\Auth\Credentials;
20
21 use Google\Auth\CredentialsLoader;
22 use Google\Auth\GetQuotaProjectInterface;
23 use Google\Auth\GetUniverseDomainInterface;
24 use Google\Auth\OAuth2;
25 use Google\Auth\UpdateMetadataTrait;
26 use InvalidArgumentException;
27 use LogicException;
28
29 /**
30 * Authenticates requests using External Account Authorized User credentials.
31 *
32 * This class allows authorizing requests from user refresh tokens sourced from
33 * external accounts.
34 */
35 class ExternalAccountAuthorizedUserCredentials extends CredentialsLoader implements GetQuotaProjectInterface
36 {
37 use RegionalAccessBoundaryTrait {
38 buildRegionalAccessBoundaryLookupUrl as traitBuildRegionalAccessBoundaryLookupUrl;
39 }
40 use UpdateMetadataTrait {
41 updateMetadata as traitUpdateMetadata;
42 }
43
44 /**
45 * Used in observability metric headers
46 *
47 * @var string
48 */
49 private const CRED_TYPE = 'eaau';
50
51 /**
52 * The OAuth2 instance used to conduct authorization.
53 */
54 private OAuth2 $auth;
55
56 private string $clientId;
57 private string $clientSecret;
58 private string $universeDomain;
59
60 /**
61 * The quota project associated with the JSON credentials
62 */
63 protected ?string $quotaProject = null;
64
65 /**
66 * Create a new ExternalAccountAuthorizedUserCredentials.
67 *
68 * @param string|string[]|null $scope the scope of the access request, expressed
69 * either as an Array or as a space-delimited String.
70 * @param array<mixed> $jsonKey JSON credential file path or JSON credentials
71 * as an associative array
72 */
73 public function __construct(
74 string|array|null $scope,
75 array $jsonKey,
76 ) {
77 if (!array_key_exists('client_id', $jsonKey)) {
78 throw new InvalidArgumentException(
79 'json key is missing the client_id field'
80 );
81 }
82 if (!array_key_exists('client_secret', $jsonKey)) {
83 throw new InvalidArgumentException(
84 'json key is missing the client_secret field'
85 );
86 }
87 if (!array_key_exists('refresh_token', $jsonKey)) {
88 throw new InvalidArgumentException(
89 'json key is missing the refresh_token field'
90 );
91 }
92 if (!array_key_exists('token_url', $jsonKey)) {
93 throw new InvalidArgumentException(
94 'json key is missing the token_url field'
95 );
96 }
97
98 $this->clientId = $jsonKey['client_id'];
99 $this->clientSecret = $jsonKey['client_secret'];
100 $this->universeDomain = $jsonKey['universe_domain'] ?? GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
101 $this->auth = new OAuth2([
102 'refresh_token' => $jsonKey['refresh_token'],
103 'tokenCredentialUri' => $jsonKey['token_url'],
104 'scope' => $scope,
105 ]);
106 if (array_key_exists('quota_project_id', $jsonKey)) {
107 $this->quotaProject = (string) $jsonKey['quota_project_id'];
108 }
109 }
110
111 /**
112 * @param callable|null $httpHandler
113 * @param array<mixed> $headers
114 *
115 * @return array<mixed> {
116 * A set of auth related metadata, containing the following
117 *
118 * @type string $access_token
119 * @type int $expires_in
120 * @type string $token_type
121 * }
122 */
123 public function fetchAuthToken(?callable $httpHandler = null, array $headers = [])
124 {
125 $headers['Authorization'] = sprintf(
126 'Basic %s',
127 base64_encode($this->clientId . ':' . $this->clientSecret)
128 );
129 return $this->auth->fetchAuthToken(
130 $httpHandler,
131 $this->applyTokenEndpointMetrics($headers, 'at')
132 );
133 }
134
135 /**
136 * Updates metadata with the authorization token.
137 *
138 * @param array<mixed> $metadata metadata hashmap
139 * @param string $authUri optional auth uri
140 * @param callable|null $httpHandler callback which delivers psr7 request
141 * @return array<mixed> updated metadata hashmap
142 */
143 public function updateMetadata(
144 $metadata,
145 $authUri = null,
146 ?callable $httpHandler = null
147 ) {
148 $metadata = $this->traitUpdateMetadata($metadata, $authUri, $httpHandler);
149
150 if ($this->enableRegionalAccessBoundary) {
151 $metadata = $this->updateRegionalAccessBoundaryMetadata(
152 $metadata,
153 $this->buildRegionalAccessBoundaryLookupUrl(),
154 $this->getUniverseDomain(),
155 $httpHandler,
156 );
157 }
158
159 return $metadata;
160 }
161
162 /**
163 * Return the Cache Key for the credentials.
164 * The format for the Cache key is
165 * Hash(ClientId.Scope.RefreshToken)
166 *
167 * @return string
168 */
169 public function getCacheKey()
170 {
171 return hash('sha256', implode('.', [
172 $this->clientId,
173 $this->auth->getScope(),
174 $this->auth->getRefreshToken()
175 ]));
176 }
177
178 /**
179 * @return array<mixed>
180 */
181 public function getLastReceivedToken()
182 {
183 return $this->auth->getLastReceivedToken();
184 }
185
186 /**
187 * Get the quota project used for this API request
188 *
189 * @return string|null
190 */
191 public function getQuotaProject(): string|null
192 {
193 return $this->quotaProject;
194 }
195
196 /**
197 * Get the universe domain used for this API request
198 *
199 * @return string
200 */
201 public function getUniverseDomain(): string
202 {
203 return $this->universeDomain;
204 }
205
206 /**
207 * Get the granted scopes (if they exist) for the last fetched token.
208 *
209 * @return string|null
210 */
211 public function getGrantedScope()
212 {
213 return $this->auth->getGrantedScope();
214 }
215
216 protected function getCredType(): string
217 {
218 return self::CRED_TYPE;
219 }
220
221 /**
222 * Builds and returns the URL for the RAB lookup API.
223 */
224 private function buildRegionalAccessBoundaryLookupUrl(): string
225 {
226 // Try to parse as a workload identity pool.
227 // Audience format: //iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
228 $regex = '/projects\/([^\/]+)\/locations\/global\/workloadIdentityPools\/([^\/]+)/';
229 if (preg_match($regex, $this->auth->getAudience(), $matches)) {
230 [$_, $projectNumber, $poolId] = $matches;
231
232 return $this->traitBuildRegionalAccessBoundaryLookupUrl(
233 poolId: $poolId,
234 projectNumber: $projectNumber,
235 );
236 }
237
238 // If that fails, try to parse as a workforce pool.
239 // Audience format: //iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID
240 if (preg_match('/locations\/[^\/]+\/workforcePools\/([^\/]+)/', $this->auth->getAudience(), $matches)) {
241 return $this->traitBuildRegionalAccessBoundaryLookupUrl(
242 poolId: $matches[1],
243 );
244 }
245
246 throw new LogicException('Invalid audience format');
247 }
248 }
249