1 /* 2 * Copyright 2018, 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 static com.google.common.base.MoreObjects.firstNonNull; 35 import static com.google.common.base.Preconditions.checkNotNull; 36 37 import com.google.api.client.http.GenericUrl; 38 import com.google.api.client.http.HttpContent; 39 import com.google.api.client.http.HttpRequest; 40 import com.google.api.client.http.HttpRequestFactory; 41 import com.google.api.client.http.HttpResponse; 42 import com.google.api.client.http.HttpTransport; 43 import com.google.api.client.http.json.JsonHttpContent; 44 import com.google.api.client.json.JsonObjectParser; 45 import com.google.api.client.util.GenericData; 46 import com.google.auth.ServiceAccountSigner; 47 import com.google.auth.http.HttpCredentialsAdapter; 48 import com.google.auth.http.HttpTransportFactory; 49 import com.google.common.annotations.VisibleForTesting; 50 import com.google.common.base.MoreObjects; 51 import com.google.common.collect.ImmutableMap; 52 import com.google.errorprone.annotations.CanIgnoreReturnValue; 53 import java.io.IOException; 54 import java.io.ObjectInputStream; 55 import java.text.DateFormat; 56 import java.text.ParseException; 57 import java.text.SimpleDateFormat; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.Calendar; 61 import java.util.Collection; 62 import java.util.Date; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.Objects; 66 67 /** 68 * ImpersonatedCredentials allowing credentials issued to a user or service account to impersonate 69 * another. The source project using ImpersonatedCredentials must enable the "IAMCredentials" API. 70 * Also, the target service account must grant the originating principal the "Service Account Token 71 * Creator" IAM role. 72 * 73 * <p>Usage: 74 * 75 * <pre> 76 * String credPath = "/path/to/svc_account.json"; 77 * ServiceAccountCredentials sourceCredentials = ServiceAccountCredentials 78 * .fromStream(new FileInputStream(credPath)); 79 * sourceCredentials = (ServiceAccountCredentials) sourceCredentials 80 * .createScoped(Arrays.asList("https://www.googleapis.com/auth/iam")); 81 * 82 * ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create(sourceCredentials, 83 * "impersonated-account@project.iam.gserviceaccount.com", null, 84 * Arrays.asList("https://www.googleapis.com/auth/devstorage.read_only"), 300); 85 * 86 * Storage storage_service = StorageOptions.newBuilder().setProjectId("project-id") 87 * .setCredentials(targetCredentials).build().getService(); 88 * 89 * for (Bucket b : storage_service.list().iterateAll()) 90 * System.out.println(b); 91 * </pre> 92 */ 93 public class ImpersonatedCredentials extends GoogleCredentials 94 implements ServiceAccountSigner, IdTokenProvider { 95 96 static final String IMPERSONATED_CREDENTIALS_FILE_TYPE = "impersonated_service_account"; 97 98 private static final long serialVersionUID = -2133257318957488431L; 99 private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ssX"; 100 private static final int TWELVE_HOURS_IN_SECONDS = 43200; 101 private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600; 102 private static final String CLOUD_PLATFORM_SCOPE = 103 "https://www.googleapis.com/auth/cloud-platform"; 104 private static final String IAM_ACCESS_TOKEN_ENDPOINT = 105 "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken"; 106 107 private GoogleCredentials sourceCredentials; 108 private String targetPrincipal; 109 private List<String> delegates; 110 private List<String> scopes; 111 private int lifetime; 112 private String iamEndpointOverride; 113 private final String transportFactoryClassName; 114 115 private transient HttpTransportFactory transportFactory; 116 117 private transient Calendar calendar; 118 119 /** 120 * @param sourceCredentials the source credential used to acquire the impersonated credentials. It 121 * should be either a user account credential or a service account credential. 122 * @param targetPrincipal the service account to impersonate 123 * @param delegates the chained list of delegates required to grant the final access_token. If 124 * set, the sequence of identities must have "Service Account Token Creator" capability 125 * granted to the preceding identity. For example, if set to [serviceAccountB, 126 * serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB. 127 * serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token 128 * Creator on target_principal. If unset, sourceCredential must have that role on 129 * targetPrincipal. 130 * @param scopes scopes to request during the authorization grant 131 * @param lifetime number of seconds the delegated credential should be valid. By default this 132 * value should be at most 3600. However, you can follow <a 133 * href='https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth'>these 134 * instructions</a> to set up the service account and extend the maximum lifetime to 43200 (12 135 * hours). If the given lifetime is 0, default value 3600 will be used instead when creating 136 * the credentials. 137 * @param transportFactory HTTP transport factory that creates the transport used to get access 138 * tokens 139 * @return new credentials 140 */ create( GoogleCredentials sourceCredentials, String targetPrincipal, List<String> delegates, List<String> scopes, int lifetime, HttpTransportFactory transportFactory)141 public static ImpersonatedCredentials create( 142 GoogleCredentials sourceCredentials, 143 String targetPrincipal, 144 List<String> delegates, 145 List<String> scopes, 146 int lifetime, 147 HttpTransportFactory transportFactory) { 148 return ImpersonatedCredentials.newBuilder() 149 .setSourceCredentials(sourceCredentials) 150 .setTargetPrincipal(targetPrincipal) 151 .setDelegates(delegates) 152 .setScopes(scopes) 153 .setLifetime(lifetime) 154 .setHttpTransportFactory(transportFactory) 155 .build(); 156 } 157 158 /** 159 * @param sourceCredentials the source credential used to acquire the impersonated credentials. It 160 * should be either a user account credential or a service account credential. 161 * @param targetPrincipal the service account to impersonate 162 * @param delegates the chained list of delegates required to grant the final access_token. If 163 * set, the sequence of identities must have "Service Account Token Creator" capability 164 * granted to the preceding identity. For example, if set to [serviceAccountB, 165 * serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB. 166 * serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token 167 * Creator on target_principal. If unset, sourceCredential must have that role on 168 * targetPrincipal. 169 * @param scopes scopes to request during the authorization grant 170 * @param lifetime number of seconds the delegated credential should be valid. By default this 171 * value should be at most 3600. However, you can follow <a 172 * href='https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth'>these 173 * instructions</a> to set up the service account and extend the maximum lifetime to 43200 (12 174 * hours). If the given lifetime is 0, default value 3600 will be used instead when creating 175 * the credentials. 176 * @param transportFactory HTTP transport factory that creates the transport used to get access 177 * tokens. 178 * @param quotaProjectId the project used for quota and billing purposes. Should be null unless 179 * the caller wants to use a project different from the one that owns the impersonated 180 * credential for billing/quota purposes. 181 * @return new credentials 182 */ create( GoogleCredentials sourceCredentials, String targetPrincipal, List<String> delegates, List<String> scopes, int lifetime, HttpTransportFactory transportFactory, String quotaProjectId)183 public static ImpersonatedCredentials create( 184 GoogleCredentials sourceCredentials, 185 String targetPrincipal, 186 List<String> delegates, 187 List<String> scopes, 188 int lifetime, 189 HttpTransportFactory transportFactory, 190 String quotaProjectId) { 191 return ImpersonatedCredentials.newBuilder() 192 .setSourceCredentials(sourceCredentials) 193 .setTargetPrincipal(targetPrincipal) 194 .setDelegates(delegates) 195 .setScopes(scopes) 196 .setLifetime(lifetime) 197 .setHttpTransportFactory(transportFactory) 198 .setQuotaProjectId(quotaProjectId) 199 .build(); 200 } 201 202 /** 203 * @param sourceCredentials the source credential used to acquire the impersonated credentials. It 204 * should be either a user account credential or a service account credential. 205 * @param targetPrincipal the service account to impersonate 206 * @param delegates the chained list of delegates required to grant the final access_token. If 207 * set, the sequence of identities must have "Service Account Token Creator" capability 208 * granted to the preceding identity. For example, if set to [serviceAccountB, 209 * serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB. 210 * serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token 211 * Creator on target_principal. If unset, sourceCredential must have that role on 212 * targetPrincipal. 213 * @param scopes scopes to request during the authorization grant 214 * @param lifetime number of seconds the delegated credential should be valid. By default this 215 * value should be at most 3600. However, you can follow <a 216 * href='https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth'>these 217 * instructions</a> to set up the service account and extend the maximum lifetime to 43200 (12 218 * hours). If the given lifetime is 0, default value 3600 will be used instead when creating 219 * the credentials. 220 * @param transportFactory HTTP transport factory that creates the transport used to get access 221 * tokens. 222 * @param quotaProjectId the project used for quota and billing purposes. Should be null unless 223 * the caller wants to use a project different from the one that owns the impersonated 224 * credential for billing/quota purposes. 225 * @param iamEndpointOverride The full IAM endpoint override with the target_principal embedded. 226 * This is useful when supporting impersonation with regional endpoints. 227 * @return new credentials 228 */ create( GoogleCredentials sourceCredentials, String targetPrincipal, List<String> delegates, List<String> scopes, int lifetime, HttpTransportFactory transportFactory, String quotaProjectId, String iamEndpointOverride)229 public static ImpersonatedCredentials create( 230 GoogleCredentials sourceCredentials, 231 String targetPrincipal, 232 List<String> delegates, 233 List<String> scopes, 234 int lifetime, 235 HttpTransportFactory transportFactory, 236 String quotaProjectId, 237 String iamEndpointOverride) { 238 return ImpersonatedCredentials.newBuilder() 239 .setSourceCredentials(sourceCredentials) 240 .setTargetPrincipal(targetPrincipal) 241 .setDelegates(delegates) 242 .setScopes(scopes) 243 .setLifetime(lifetime) 244 .setHttpTransportFactory(transportFactory) 245 .setQuotaProjectId(quotaProjectId) 246 .setIamEndpointOverride(iamEndpointOverride) 247 .build(); 248 } 249 250 /** 251 * @param sourceCredentials the source credential used to acquire the impersonated credentials. It 252 * should be either a user account credential or a service account credential. 253 * @param targetPrincipal the service account to impersonate 254 * @param delegates the chained list of delegates required to grant the final access_token. If 255 * set, the sequence of identities must have "Service Account Token Creator" capability 256 * granted to the preceding identity. For example, if set to [serviceAccountB, 257 * serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB. 258 * serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token 259 * Creator on target_principal. If left unset, sourceCredential must have that role on 260 * targetPrincipal. 261 * @param scopes scopes to request during the authorization grant 262 * @param lifetime number of seconds the delegated credential should be valid. By default this 263 * value should be at most 3600. However, you can follow <a 264 * href='https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth'>these 265 * instructions</a> to set up the service account and extend the maximum lifetime to 43200 (12 266 * hours). 267 * https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth 268 * If the given lifetime is 0, default value 3600 will be used instead when creating the 269 * credentials. 270 * @return new credentials 271 */ create( GoogleCredentials sourceCredentials, String targetPrincipal, List<String> delegates, List<String> scopes, int lifetime)272 public static ImpersonatedCredentials create( 273 GoogleCredentials sourceCredentials, 274 String targetPrincipal, 275 List<String> delegates, 276 List<String> scopes, 277 int lifetime) { 278 return ImpersonatedCredentials.newBuilder() 279 .setSourceCredentials(sourceCredentials) 280 .setTargetPrincipal(targetPrincipal) 281 .setDelegates(delegates) 282 .setScopes(scopes) 283 .setLifetime(lifetime) 284 .build(); 285 } 286 extractTargetPrincipal(String serviceAccountImpersonationUrl)287 static String extractTargetPrincipal(String serviceAccountImpersonationUrl) { 288 // Extract the target principal. 289 int startIndex = serviceAccountImpersonationUrl.lastIndexOf('/'); 290 int endIndex = serviceAccountImpersonationUrl.indexOf(":generateAccessToken"); 291 292 if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) { 293 return serviceAccountImpersonationUrl.substring(startIndex + 1, endIndex); 294 } else { 295 throw new IllegalArgumentException( 296 "Unable to determine target principal from service account impersonation URL."); 297 } 298 } 299 300 /** 301 * Returns the email field of the serviceAccount that is being impersonated. 302 * 303 * @return email address of the impersonated service account 304 */ 305 @Override getAccount()306 public String getAccount() { 307 return this.targetPrincipal; 308 } 309 310 @VisibleForTesting getIamEndpointOverride()311 String getIamEndpointOverride() { 312 return this.iamEndpointOverride; 313 } 314 315 @VisibleForTesting getDelegates()316 List<String> getDelegates() { 317 return delegates; 318 } 319 320 @VisibleForTesting getScopes()321 List<String> getScopes() { 322 return scopes; 323 } 324 getSourceCredentials()325 public GoogleCredentials getSourceCredentials() { 326 return sourceCredentials; 327 } 328 getLifetime()329 int getLifetime() { 330 return this.lifetime; 331 } 332 setTransportFactory(HttpTransportFactory httpTransportFactory)333 public void setTransportFactory(HttpTransportFactory httpTransportFactory) { 334 this.transportFactory = httpTransportFactory; 335 } 336 337 /** 338 * Signs the provided bytes using the private key associated with the impersonated service account 339 * 340 * @param toSign bytes to sign 341 * @return signed bytes 342 * @throws SigningException if the attempt to sign the provided bytes failed 343 * @see <a 344 * href="https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/signBlob">Blob 345 * Signing</a> 346 */ 347 @Override sign(byte[] toSign)348 public byte[] sign(byte[] toSign) { 349 return IamUtils.sign( 350 getAccount(), 351 sourceCredentials, 352 transportFactory.create(), 353 toSign, 354 ImmutableMap.of("delegates", this.delegates)); 355 } 356 357 /** 358 * Returns impersonation account credentials defined by JSON using the format generated by gCloud. 359 * The source credentials in the JSON should be either user account credentials or service account 360 * credentials. 361 * 362 * @param json a map from the JSON representing the credentials 363 * @param transportFactory HTTP transport factory, creates the transport used to get access tokens 364 * @return the credentials defined by the JSON 365 * @throws IOException if the credential cannot be created from the JSON. 366 */ 367 @SuppressWarnings("unchecked") fromJson( Map<String, Object> json, HttpTransportFactory transportFactory)368 static ImpersonatedCredentials fromJson( 369 Map<String, Object> json, HttpTransportFactory transportFactory) throws IOException { 370 371 checkNotNull(json); 372 checkNotNull(transportFactory); 373 374 List<String> delegates = null; 375 Map<String, Object> sourceCredentialsJson; 376 String sourceCredentialsType; 377 String quotaProjectId; 378 String targetPrincipal; 379 String serviceAccountImpersonationUrl; 380 try { 381 serviceAccountImpersonationUrl = (String) json.get("service_account_impersonation_url"); 382 if (json.containsKey("delegates")) { 383 delegates = (List<String>) json.get("delegates"); 384 } 385 sourceCredentialsJson = (Map<String, Object>) json.get("source_credentials"); 386 sourceCredentialsType = (String) sourceCredentialsJson.get("type"); 387 quotaProjectId = (String) json.get("quota_project_id"); 388 targetPrincipal = extractTargetPrincipal(serviceAccountImpersonationUrl); 389 } catch (ClassCastException | NullPointerException | IllegalArgumentException e) { 390 throw new CredentialFormatException("An invalid input stream was provided.", e); 391 } 392 393 GoogleCredentials sourceCredentials; 394 if (GoogleCredentials.USER_FILE_TYPE.equals(sourceCredentialsType)) { 395 sourceCredentials = UserCredentials.fromJson(sourceCredentialsJson, transportFactory); 396 } else if (GoogleCredentials.SERVICE_ACCOUNT_FILE_TYPE.equals(sourceCredentialsType)) { 397 sourceCredentials = 398 ServiceAccountCredentials.fromJson(sourceCredentialsJson, transportFactory); 399 } else { 400 throw new IOException( 401 String.format( 402 "A credential of type %s is not supported as source credential for impersonation.", 403 sourceCredentialsType)); 404 } 405 return ImpersonatedCredentials.newBuilder() 406 .setSourceCredentials(sourceCredentials) 407 .setTargetPrincipal(targetPrincipal) 408 .setDelegates(delegates) 409 .setScopes(new ArrayList<String>()) 410 .setLifetime(DEFAULT_LIFETIME_IN_SECONDS) 411 .setHttpTransportFactory(transportFactory) 412 .setQuotaProjectId(quotaProjectId) 413 .setIamEndpointOverride(serviceAccountImpersonationUrl) 414 .build(); 415 } 416 417 @Override createScopedRequired()418 public boolean createScopedRequired() { 419 return this.scopes == null || this.scopes.isEmpty(); 420 } 421 422 @Override createScoped(Collection<String> scopes)423 public GoogleCredentials createScoped(Collection<String> scopes) { 424 return toBuilder() 425 .setScopes(new ArrayList<>(scopes)) 426 .setLifetime(this.lifetime) 427 .setDelegates(this.delegates) 428 .setHttpTransportFactory(this.transportFactory) 429 .setQuotaProjectId(this.quotaProjectId) 430 .setIamEndpointOverride(this.iamEndpointOverride) 431 .build(); 432 } 433 434 /** 435 * Clones the impersonated credentials with a new calendar. 436 * 437 * @param calendar the calendar that will be used by the new ImpersonatedCredentials instance when 438 * parsing the received expiration time of the refreshed access token 439 * @return the cloned impersonated credentials with the given custom calendar 440 */ createWithCustomCalendar(Calendar calendar)441 public ImpersonatedCredentials createWithCustomCalendar(Calendar calendar) { 442 return toBuilder() 443 .setScopes(this.scopes) 444 .setLifetime(this.lifetime) 445 .setDelegates(this.delegates) 446 .setHttpTransportFactory(this.transportFactory) 447 .setQuotaProjectId(this.quotaProjectId) 448 .setIamEndpointOverride(this.iamEndpointOverride) 449 .setCalendar(calendar) 450 .build(); 451 } 452 ImpersonatedCredentials(Builder builder)453 private ImpersonatedCredentials(Builder builder) { 454 super(builder); 455 this.sourceCredentials = builder.getSourceCredentials(); 456 this.targetPrincipal = builder.getTargetPrincipal(); 457 this.delegates = builder.getDelegates(); 458 this.scopes = builder.getScopes(); 459 this.lifetime = builder.getLifetime(); 460 this.transportFactory = 461 firstNonNull( 462 builder.getHttpTransportFactory(), 463 getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); 464 this.iamEndpointOverride = builder.iamEndpointOverride; 465 this.transportFactoryClassName = this.transportFactory.getClass().getName(); 466 this.calendar = builder.getCalendar(); 467 if (this.delegates == null) { 468 this.delegates = new ArrayList<String>(); 469 } 470 if (this.scopes == null) { 471 throw new IllegalStateException("Scopes cannot be null"); 472 } 473 if (this.lifetime > TWELVE_HOURS_IN_SECONDS) { 474 throw new IllegalStateException("lifetime must be less than or equal to 43200"); 475 } 476 } 477 478 @Override refreshAccessToken()479 public AccessToken refreshAccessToken() throws IOException { 480 if (this.sourceCredentials.getAccessToken() == null) { 481 this.sourceCredentials = 482 this.sourceCredentials.createScoped(Arrays.asList(CLOUD_PLATFORM_SCOPE)); 483 } 484 485 try { 486 this.sourceCredentials.refreshIfExpired(); 487 } catch (IOException e) { 488 throw new IOException("Unable to refresh sourceCredentials", e); 489 } 490 491 HttpTransport httpTransport = this.transportFactory.create(); 492 JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); 493 494 HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(sourceCredentials); 495 HttpRequestFactory requestFactory = httpTransport.createRequestFactory(); 496 497 String endpointUrl = 498 this.iamEndpointOverride != null 499 ? this.iamEndpointOverride 500 : String.format(IAM_ACCESS_TOKEN_ENDPOINT, this.targetPrincipal); 501 GenericUrl url = new GenericUrl(endpointUrl); 502 503 Map<String, Object> body = 504 ImmutableMap.<String, Object>of( 505 "delegates", this.delegates, "scope", this.scopes, "lifetime", this.lifetime + "s"); 506 507 HttpContent requestContent = new JsonHttpContent(parser.getJsonFactory(), body); 508 HttpRequest request = requestFactory.buildPostRequest(url, requestContent); 509 adapter.initialize(request); 510 request.setParser(parser); 511 512 HttpResponse response = null; 513 try { 514 response = request.execute(); 515 } catch (IOException e) { 516 throw new IOException("Error requesting access token", e); 517 } 518 519 GenericData responseData = response.parseAs(GenericData.class); 520 response.disconnect(); 521 522 String accessToken = 523 OAuth2Utils.validateString(responseData, "accessToken", "Expected to find an accessToken"); 524 String expireTime = 525 OAuth2Utils.validateString(responseData, "expireTime", "Expected to find an expireTime"); 526 527 DateFormat format = new SimpleDateFormat(RFC3339); 528 format.setCalendar(calendar); 529 try { 530 Date date = format.parse(expireTime); 531 return new AccessToken(accessToken, date); 532 } catch (ParseException pe) { 533 throw new IOException("Error parsing expireTime: " + pe.getMessage()); 534 } 535 } 536 537 /** 538 * Returns an IdToken for the current Credential. 539 * 540 * @param targetAudience the audience field for the issued ID token 541 * @param options credential specific options for for the token. For example, an ID token for an 542 * ImpersonatedCredentials can return the email address within the token claims if 543 * "ImpersonatedCredentials.INCLUDE_EMAIL" is provided as a list option.<br> 544 * Only one option value is supported: "ImpersonatedCredentials.INCLUDE_EMAIL" If no options 545 * are set, the default excludes the "includeEmail" attribute in the API request. 546 * @return IdToken object which includes the raw id_token, expiration, and audience 547 * @throws IOException if the attempt to get an ID token failed 548 */ 549 @Override idTokenWithAudience(String targetAudience, List<IdTokenProvider.Option> options)550 public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.Option> options) 551 throws IOException { 552 boolean includeEmail = 553 options != null && options.contains(IdTokenProvider.Option.INCLUDE_EMAIL); 554 return IamUtils.getIdToken( 555 getAccount(), 556 sourceCredentials, 557 transportFactory.create(), 558 targetAudience, 559 includeEmail, 560 ImmutableMap.of("delegates", this.delegates)); 561 } 562 563 @Override hashCode()564 public int hashCode() { 565 return Objects.hash( 566 sourceCredentials, 567 targetPrincipal, 568 delegates, 569 scopes, 570 lifetime, 571 quotaProjectId, 572 iamEndpointOverride); 573 } 574 575 @Override toString()576 public String toString() { 577 return MoreObjects.toStringHelper(this) 578 .add("sourceCredentials", sourceCredentials) 579 .add("targetPrincipal", targetPrincipal) 580 .add("delegates", delegates) 581 .add("scopes", scopes) 582 .add("lifetime", lifetime) 583 .add("transportFactoryClassName", transportFactoryClassName) 584 .add("quotaProjectId", quotaProjectId) 585 .add("iamEndpointOverride", iamEndpointOverride) 586 .toString(); 587 } 588 589 @Override equals(Object obj)590 public boolean equals(Object obj) { 591 if (!(obj instanceof ImpersonatedCredentials)) { 592 return false; 593 } 594 ImpersonatedCredentials other = (ImpersonatedCredentials) obj; 595 return Objects.equals(this.sourceCredentials, other.sourceCredentials) 596 && Objects.equals(this.targetPrincipal, other.targetPrincipal) 597 && Objects.equals(this.delegates, other.delegates) 598 && Objects.equals(this.scopes, other.scopes) 599 && Objects.equals(this.lifetime, other.lifetime) 600 && Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName) 601 && Objects.equals(this.quotaProjectId, other.quotaProjectId) 602 && Objects.equals(this.iamEndpointOverride, other.iamEndpointOverride); 603 } 604 605 @Override toBuilder()606 public Builder toBuilder() { 607 return new Builder(this.sourceCredentials, this.targetPrincipal); 608 } 609 newBuilder()610 public static Builder newBuilder() { 611 return new Builder(); 612 } 613 614 public static class Builder extends GoogleCredentials.Builder { 615 616 private GoogleCredentials sourceCredentials; 617 private String targetPrincipal; 618 private List<String> delegates; 619 private List<String> scopes; 620 private int lifetime = DEFAULT_LIFETIME_IN_SECONDS; 621 private HttpTransportFactory transportFactory; 622 private String iamEndpointOverride; 623 private Calendar calendar = Calendar.getInstance(); 624 Builder()625 protected Builder() {} 626 Builder(GoogleCredentials sourceCredentials, String targetPrincipal)627 protected Builder(GoogleCredentials sourceCredentials, String targetPrincipal) { 628 this.sourceCredentials = sourceCredentials; 629 this.targetPrincipal = targetPrincipal; 630 } 631 632 @CanIgnoreReturnValue setSourceCredentials(GoogleCredentials sourceCredentials)633 public Builder setSourceCredentials(GoogleCredentials sourceCredentials) { 634 this.sourceCredentials = sourceCredentials; 635 return this; 636 } 637 getSourceCredentials()638 public GoogleCredentials getSourceCredentials() { 639 return this.sourceCredentials; 640 } 641 642 @CanIgnoreReturnValue setTargetPrincipal(String targetPrincipal)643 public Builder setTargetPrincipal(String targetPrincipal) { 644 this.targetPrincipal = targetPrincipal; 645 return this; 646 } 647 getTargetPrincipal()648 public String getTargetPrincipal() { 649 return this.targetPrincipal; 650 } 651 652 @CanIgnoreReturnValue setDelegates(List<String> delegates)653 public Builder setDelegates(List<String> delegates) { 654 this.delegates = delegates; 655 return this; 656 } 657 getDelegates()658 public List<String> getDelegates() { 659 return this.delegates; 660 } 661 662 @CanIgnoreReturnValue setScopes(List<String> scopes)663 public Builder setScopes(List<String> scopes) { 664 this.scopes = scopes; 665 return this; 666 } 667 getScopes()668 public List<String> getScopes() { 669 return this.scopes; 670 } 671 672 @CanIgnoreReturnValue setLifetime(int lifetime)673 public Builder setLifetime(int lifetime) { 674 this.lifetime = lifetime == 0 ? DEFAULT_LIFETIME_IN_SECONDS : lifetime; 675 return this; 676 } 677 getLifetime()678 public int getLifetime() { 679 return this.lifetime; 680 } 681 682 @CanIgnoreReturnValue setHttpTransportFactory(HttpTransportFactory transportFactory)683 public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) { 684 this.transportFactory = transportFactory; 685 return this; 686 } 687 getHttpTransportFactory()688 public HttpTransportFactory getHttpTransportFactory() { 689 return transportFactory; 690 } 691 692 @Override 693 @CanIgnoreReturnValue setQuotaProjectId(String quotaProjectId)694 public Builder setQuotaProjectId(String quotaProjectId) { 695 super.setQuotaProjectId(quotaProjectId); 696 return this; 697 } 698 699 @CanIgnoreReturnValue setIamEndpointOverride(String iamEndpointOverride)700 public Builder setIamEndpointOverride(String iamEndpointOverride) { 701 this.iamEndpointOverride = iamEndpointOverride; 702 return this; 703 } 704 705 @CanIgnoreReturnValue setCalendar(Calendar calendar)706 public Builder setCalendar(Calendar calendar) { 707 this.calendar = calendar; 708 return this; 709 } 710 getCalendar()711 public Calendar getCalendar() { 712 return this.calendar; 713 } 714 715 @Override build()716 public ImpersonatedCredentials build() { 717 return new ImpersonatedCredentials(this); 718 } 719 } 720 readObject(ObjectInputStream input)721 private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { 722 input.defaultReadObject(); 723 transportFactory = newInstance(transportFactoryClassName); 724 } 725 } 726