1 /* 2 * Copyright 2015, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package com.google.auth.oauth2; 33 34 import com.google.api.client.json.GenericJson; 35 import com.google.api.client.json.JsonFactory; 36 import com.google.api.client.json.JsonObjectParser; 37 import com.google.api.client.util.Preconditions; 38 import com.google.auth.Credentials; 39 import com.google.auth.http.HttpTransportFactory; 40 import com.google.common.annotations.VisibleForTesting; 41 import com.google.common.base.MoreObjects; 42 import com.google.common.base.MoreObjects.ToStringHelper; 43 import com.google.common.collect.ImmutableList; 44 import com.google.errorprone.annotations.CanIgnoreReturnValue; 45 import java.io.IOException; 46 import java.io.InputStream; 47 import java.nio.charset.StandardCharsets; 48 import java.time.Duration; 49 import java.util.Collection; 50 import java.util.Collections; 51 import java.util.HashMap; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Objects; 55 import javax.annotation.Nullable; 56 57 /** Base type for credentials for authorizing calls to Google APIs using OAuth2. */ 58 public class GoogleCredentials extends OAuth2Credentials implements QuotaProjectIdProvider { 59 60 private static final long serialVersionUID = -1522852442442473691L; 61 62 static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project"; 63 static final String USER_FILE_TYPE = "authorized_user"; 64 static final String SERVICE_ACCOUNT_FILE_TYPE = "service_account"; 65 static final String GDCH_SERVICE_ACCOUNT_FILE_TYPE = "gdch_service_account"; 66 67 private final String universeDomain; 68 private final boolean isExplicitUniverseDomain; 69 70 protected final String quotaProjectId; 71 72 private static final DefaultCredentialsProvider defaultCredentialsProvider = 73 new DefaultCredentialsProvider(); 74 75 /** 76 * Returns the credentials instance from the given access token. 77 * 78 * @param accessToken the access token 79 * @return the credentials instance 80 */ create(AccessToken accessToken)81 public static GoogleCredentials create(AccessToken accessToken) { 82 return GoogleCredentials.newBuilder().setAccessToken(accessToken).build(); 83 } 84 85 /** 86 * Returns the credentials instance from the given access token and universe domain. 87 * 88 * @param universeDomain the universe domain 89 * @param accessToken the access token 90 * @return the credentials instance 91 */ create(String universeDomain, AccessToken accessToken)92 public static GoogleCredentials create(String universeDomain, AccessToken accessToken) { 93 return GoogleCredentials.newBuilder() 94 .setAccessToken(accessToken) 95 .setUniverseDomain(universeDomain) 96 .build(); 97 } 98 99 /** 100 * Returns the Application Default Credentials. 101 * 102 * <p>Returns the Application Default Credentials which are used to identify and authorize the 103 * whole application. The following are searched (in order) to find the Application Default 104 * Credentials: 105 * 106 * <ol> 107 * <li>Credentials file pointed to by the {@code GOOGLE_APPLICATION_CREDENTIALS} environment 108 * variable 109 * <li>Credentials provided by the Google Cloud SDK. 110 * <ol> 111 * <li>{@code gcloud auth application-default login} for user account credentials. 112 * <li>{@code gcloud auth application-default login --impersonate-service-account} for 113 * impersonated service account credentials. 114 * </ol> 115 * <li>Google App Engine built-in credentials 116 * <li>Google Cloud Shell built-in credentials 117 * <li>Google Compute Engine built-in credentials 118 * </ol> 119 * 120 * @return the credentials instance. 121 * @throws IOException if the credentials cannot be created in the current environment. 122 */ getApplicationDefault()123 public static GoogleCredentials getApplicationDefault() throws IOException { 124 return getApplicationDefault(OAuth2Utils.HTTP_TRANSPORT_FACTORY); 125 } 126 127 /** 128 * Returns the Application Default Credentials. 129 * 130 * <p>Returns the Application Default Credentials which are used to identify and authorize the 131 * whole application. The following are searched (in order) to find the Application Default 132 * Credentials: 133 * 134 * <ol> 135 * <li>Credentials file pointed to by the {@code GOOGLE_APPLICATION_CREDENTIALS} environment 136 * variable 137 * <li>Credentials provided by the Google Cloud SDK {@code gcloud auth application-default 138 * login} command 139 * <li>Google App Engine built-in credentials 140 * <li>Google Cloud Shell built-in credentials 141 * <li>Google Compute Engine built-in credentials 142 * </ol> 143 * 144 * @param transportFactory HTTP transport factory, creates the transport used to get access 145 * tokens. 146 * @return the credentials instance. 147 * @throws IOException if the credentials cannot be created in the current environment. 148 */ getApplicationDefault(HttpTransportFactory transportFactory)149 public static GoogleCredentials getApplicationDefault(HttpTransportFactory transportFactory) 150 throws IOException { 151 Preconditions.checkNotNull(transportFactory); 152 return defaultCredentialsProvider.getDefaultCredentials(transportFactory); 153 } 154 155 /** 156 * Returns credentials defined by a JSON file stream. 157 * 158 * <p>The stream can contain a Service Account key file in JSON format from the Google Developers 159 * Console or a stored user credential using the format supported by the Cloud SDK. 160 * 161 * @param credentialsStream the stream with the credential definition. 162 * @return the credential defined by the credentialsStream. 163 * @throws IOException if the credential cannot be created from the stream. 164 */ fromStream(InputStream credentialsStream)165 public static GoogleCredentials fromStream(InputStream credentialsStream) throws IOException { 166 return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); 167 } 168 169 /** 170 * Returns credentials defined by a JSON file stream. 171 * 172 * <p>The stream can contain a Service Account key file in JSON format from the Google Developers 173 * Console or a stored user credential using the format supported by the Cloud SDK. 174 * 175 * @param credentialsStream the stream with the credential definition. 176 * @param transportFactory HTTP transport factory, creates the transport used to get access 177 * tokens. 178 * @return the credential defined by the credentialsStream. 179 * @throws IOException if the credential cannot be created from the stream. 180 */ fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory)181 public static GoogleCredentials fromStream( 182 InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException { 183 Preconditions.checkNotNull(credentialsStream); 184 Preconditions.checkNotNull(transportFactory); 185 186 JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; 187 JsonObjectParser parser = new JsonObjectParser(jsonFactory); 188 GenericJson fileContents = 189 parser.parseAndClose(credentialsStream, StandardCharsets.UTF_8, GenericJson.class); 190 191 String fileType = (String) fileContents.get("type"); 192 if (fileType == null) { 193 throw new IOException("Error reading credentials from stream, 'type' field not specified."); 194 } 195 196 if (USER_FILE_TYPE.equals(fileType)) { 197 return UserCredentials.fromJson(fileContents, transportFactory); 198 } 199 if (SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) { 200 return ServiceAccountCredentials.fromJson(fileContents, transportFactory); 201 } 202 if (GDCH_SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) { 203 return GdchCredentials.fromJson(fileContents); 204 } 205 if (ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE.equals(fileType)) { 206 return ExternalAccountCredentials.fromJson(fileContents, transportFactory); 207 } 208 if (ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE.equals( 209 fileType)) { 210 return ExternalAccountAuthorizedUserCredentials.fromJson(fileContents, transportFactory); 211 } 212 if (ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE.equals(fileType)) { 213 return ImpersonatedCredentials.fromJson(fileContents, transportFactory); 214 } 215 throw new IOException( 216 String.format( 217 "Error reading credentials from stream, 'type' value '%s' not recognized." 218 + " Valid values are '%s', '%s', '%s', '%s', '%s', '%s'.", 219 fileType, 220 USER_FILE_TYPE, 221 SERVICE_ACCOUNT_FILE_TYPE, 222 GDCH_SERVICE_ACCOUNT_FILE_TYPE, 223 ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE, 224 ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE, 225 ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE)); 226 } 227 228 /** 229 * Creates a credential with the provided quota project. 230 * 231 * @param quotaProject the quota project to set on the credential 232 * @return credential with the provided quota project 233 */ createWithQuotaProject(String quotaProject)234 public GoogleCredentials createWithQuotaProject(String quotaProject) { 235 return this.toBuilder().setQuotaProjectId(quotaProject).build(); 236 } 237 238 /** 239 * Gets the universe domain for the credential. 240 * 241 * @return An explicit universe domain if it was explicitly provided, invokes the super 242 * implementation otherwise 243 */ 244 @Override getUniverseDomain()245 public String getUniverseDomain() throws IOException { 246 return this.universeDomain; 247 } 248 249 /** 250 * Gets the flag indicating whether universeDomain was explicitly set by the developer. 251 * 252 * <p>If subclass has a requirement to give priority to developer-set universeDomain, this 253 * property must be used to check if the universeDomain value was provided by the user. It could 254 * be a default otherwise. 255 * 256 * @return true if universeDomain value was provided by the developer, false otherwise 257 */ 258 @VisibleForTesting isExplicitUniverseDomain()259 protected boolean isExplicitUniverseDomain() { 260 return this.isExplicitUniverseDomain; 261 } 262 263 /** 264 * Checks if universe domain equals to {@link Credentials#GOOGLE_DEFAULT_UNIVERSE}. 265 * 266 * @return true if universeDomain equals to {@link Credentials#GOOGLE_DEFAULT_UNIVERSE}, false 267 * otherwise 268 */ isDefaultUniverseDomain()269 boolean isDefaultUniverseDomain() { 270 return this.universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE); 271 } 272 273 /** 274 * Adds quota project ID to requestMetadata if present. 275 * 276 * @return a new map with quotaProjectId added if needed 277 */ addQuotaProjectIdToRequestMetadata( String quotaProjectId, Map<String, List<String>> requestMetadata)278 static Map<String, List<String>> addQuotaProjectIdToRequestMetadata( 279 String quotaProjectId, Map<String, List<String>> requestMetadata) { 280 Preconditions.checkNotNull(requestMetadata); 281 Map<String, List<String>> newRequestMetadata = new HashMap<>(requestMetadata); 282 if (quotaProjectId != null && !requestMetadata.containsKey(QUOTA_PROJECT_ID_HEADER_KEY)) { 283 newRequestMetadata.put( 284 QUOTA_PROJECT_ID_HEADER_KEY, Collections.singletonList(quotaProjectId)); 285 } 286 return Collections.unmodifiableMap(newRequestMetadata); 287 } 288 289 @Override getAdditionalHeaders()290 protected Map<String, List<String>> getAdditionalHeaders() { 291 Map<String, List<String>> headers = super.getAdditionalHeaders(); 292 String quotaProjectId = this.getQuotaProjectId(); 293 if (quotaProjectId != null) { 294 return addQuotaProjectIdToRequestMetadata(quotaProjectId, headers); 295 } 296 return headers; 297 } 298 299 /** Default constructor. */ GoogleCredentials()300 protected GoogleCredentials() { 301 this(new Builder()); 302 } 303 304 /** 305 * Constructor with an explicit access token and quotaProjectId. 306 * 307 * <p>Deprecated, please use the {@link GoogleCredentials#GoogleCredentials(Builder)} constructor 308 * whenever possible. 309 * 310 * @param accessToken initial or temporary access token 311 * @param quotaProjectId a quotaProjectId, a project id to be used for billing purposes 312 */ 313 @Deprecated GoogleCredentials(AccessToken accessToken, String quotaProjectId)314 protected GoogleCredentials(AccessToken accessToken, String quotaProjectId) { 315 this( 316 GoogleCredentials.newBuilder() 317 .setAccessToken(accessToken) 318 .setQuotaProjectId(quotaProjectId)); 319 } 320 321 /** 322 * Constructor with explicit access token. 323 * 324 * @param accessToken initial or temporary access token 325 */ 326 @Deprecated GoogleCredentials(AccessToken accessToken)327 public GoogleCredentials(AccessToken accessToken) { 328 this(accessToken, null); 329 } 330 331 /** 332 * Constructor that relies on a {@link GoogleCredential.Builder} to provide all the necessary 333 * field values for initialization. 334 * 335 * @param builder an instance of a builder 336 */ GoogleCredentials(Builder builder)337 protected GoogleCredentials(Builder builder) { 338 super(builder.getAccessToken(), builder.getRefreshMargin(), builder.getExpirationMargin()); 339 this.quotaProjectId = builder.getQuotaProjectId(); 340 341 if (builder.universeDomain == null || builder.universeDomain.trim().isEmpty()) { 342 this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; 343 this.isExplicitUniverseDomain = false; 344 } else { 345 this.universeDomain = builder.getUniverseDomain(); 346 this.isExplicitUniverseDomain = true; 347 } 348 } 349 350 /** 351 * Constructor with explicit access token and refresh margins. 352 * 353 * <p>Deprecated, please use the {@link GoogleCredentials#GoogleCredentials(Builder)} constructor 354 * whenever possible. 355 * 356 * @param accessToken initial or temporary access token 357 */ 358 @Deprecated GoogleCredentials( AccessToken accessToken, Duration refreshMargin, Duration expirationMargin)359 protected GoogleCredentials( 360 AccessToken accessToken, Duration refreshMargin, Duration expirationMargin) { 361 this( 362 (Builder) 363 GoogleCredentials.newBuilder() 364 .setAccessToken(accessToken) 365 .setRefreshMargin(refreshMargin) 366 .setExpirationMargin(expirationMargin)); 367 } 368 369 /** 370 * A helper for overriding the toString() method. This allows inheritance of super class fields. 371 * Extending classes can override this implementation and call super implementation and add more 372 * fields. Same cannot be done with overriding the toString() directly. 373 * 374 * @return an instance of the ToStringHelper that has public fields added 375 */ toStringHelper()376 protected ToStringHelper toStringHelper() { 377 return MoreObjects.toStringHelper(this) 378 .omitNullValues() 379 .add("quotaProjectId", this.quotaProjectId) 380 .add("universeDomain", this.universeDomain) 381 .add("isExplicitUniverseDomain", this.isExplicitUniverseDomain); 382 } 383 384 @Override toString()385 public String toString() { 386 return toStringHelper().toString(); 387 } 388 389 @Override equals(Object obj)390 public boolean equals(Object obj) { 391 if (!(obj instanceof GoogleCredentials)) { 392 return false; 393 } 394 GoogleCredentials other = (GoogleCredentials) obj; 395 return Objects.equals(this.quotaProjectId, other.quotaProjectId) 396 && Objects.equals(this.universeDomain, other.universeDomain) 397 && Objects.equals(this.isExplicitUniverseDomain, other.isExplicitUniverseDomain); 398 } 399 400 @Override hashCode()401 public int hashCode() { 402 return Objects.hash(this.quotaProjectId, this.universeDomain, this.isExplicitUniverseDomain); 403 } 404 newBuilder()405 public static Builder newBuilder() { 406 return new Builder(); 407 } 408 409 @Override toBuilder()410 public Builder toBuilder() { 411 return new Builder(this); 412 } 413 414 @Override getQuotaProjectId()415 public String getQuotaProjectId() { 416 return this.quotaProjectId; 417 } 418 419 /** 420 * Indicates whether the credentials require scopes to be specified via a call to {@link 421 * GoogleCredentials#createScoped} before use. 422 * 423 * @return Whether the credentials require scopes to be specified. 424 */ createScopedRequired()425 public boolean createScopedRequired() { 426 return false; 427 } 428 429 /** 430 * If the credentials support scopes, creates a copy of the identity with the specified scopes; 431 * otherwise, returns the same instance. 432 * 433 * @param scopes Collection of scopes to request. 434 * @return GoogleCredentials with requested scopes. 435 */ createScoped(Collection<String> scopes)436 public GoogleCredentials createScoped(Collection<String> scopes) { 437 return this; 438 } 439 440 /** 441 * If the credentials support scopes, creates a copy of the identity with the specified scopes and 442 * default scopes; otherwise, returns the same instance. This is mainly used by client libraries. 443 * 444 * @param scopes Collection of scopes to request. 445 * @param defaultScopes Collection of default scopes to request. 446 * @return GoogleCredentials with requested scopes. 447 */ createScoped( Collection<String> scopes, Collection<String> defaultScopes)448 public GoogleCredentials createScoped( 449 Collection<String> scopes, Collection<String> defaultScopes) { 450 return this; 451 } 452 453 /** 454 * If the credentials support scopes, creates a copy of the identity with the specified scopes; 455 * otherwise, returns the same instance. 456 * 457 * @param scopes Collection of scopes to request. 458 * @return GoogleCredentials with requested scopes. 459 */ createScoped(String... scopes)460 public GoogleCredentials createScoped(String... scopes) { 461 return createScoped(ImmutableList.copyOf(scopes)); 462 } 463 464 /** 465 * If the credentials support automatic retries, creates a copy of the identity with the provided 466 * retry strategy 467 * 468 * @param defaultRetriesEnabled a flag enabling or disabling default retries 469 * @return GoogleCredentials with the new default retries configuration. 470 */ createWithCustomRetryStrategy(boolean defaultRetriesEnabled)471 public GoogleCredentials createWithCustomRetryStrategy(boolean defaultRetriesEnabled) { 472 return this; 473 } 474 475 /** 476 * If the credentials support domain-wide delegation, creates a copy of the identity so that it 477 * impersonates the specified user; otherwise, returns the same instance. 478 * 479 * @param user User to impersonate. 480 * @return GoogleCredentials with a delegated user. 481 */ createDelegated(String user)482 public GoogleCredentials createDelegated(String user) { 483 return this; 484 } 485 486 public static class Builder extends OAuth2Credentials.Builder { 487 @Nullable protected String quotaProjectId; 488 @Nullable protected String universeDomain; 489 Builder()490 protected Builder() {} 491 Builder(GoogleCredentials credentials)492 protected Builder(GoogleCredentials credentials) { 493 super(credentials); 494 this.quotaProjectId = credentials.quotaProjectId; 495 if (credentials.isExplicitUniverseDomain) { 496 this.universeDomain = credentials.universeDomain; 497 } 498 } 499 Builder(GoogleCredentials.Builder builder)500 protected Builder(GoogleCredentials.Builder builder) { 501 setAccessToken(builder.getAccessToken()); 502 this.quotaProjectId = builder.quotaProjectId; 503 this.universeDomain = builder.universeDomain; 504 } 505 506 @Override build()507 public GoogleCredentials build() { 508 return new GoogleCredentials(this); 509 } 510 511 @CanIgnoreReturnValue setQuotaProjectId(String quotaProjectId)512 public Builder setQuotaProjectId(String quotaProjectId) { 513 this.quotaProjectId = quotaProjectId; 514 return this; 515 } 516 setUniverseDomain(String universeDomain)517 public Builder setUniverseDomain(String universeDomain) { 518 this.universeDomain = universeDomain; 519 return this; 520 } 521 getQuotaProjectId()522 public String getQuotaProjectId() { 523 return this.quotaProjectId; 524 } 525 getUniverseDomain()526 public String getUniverseDomain() { 527 return this.universeDomain; 528 } 529 530 @Override 531 @CanIgnoreReturnValue setAccessToken(AccessToken token)532 public Builder setAccessToken(AccessToken token) { 533 super.setAccessToken(token); 534 return this; 535 } 536 } 537 } 538