• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016, 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.auth.ServiceAccountSigner;
35 import com.google.common.base.MoreObjects;
36 import com.google.common.collect.ImmutableList;
37 import com.google.common.collect.ImmutableSet;
38 import java.io.IOException;
39 import java.io.ObjectInputStream;
40 import java.lang.reflect.InvocationTargetException;
41 import java.lang.reflect.Method;
42 import java.util.Collection;
43 import java.util.Date;
44 import java.util.Objects;
45 
46 /**
47  * OAuth2 credentials representing the built-in service account for Google App Engine.
48  *
49  * <p>Instances of this class use reflection to access AppIdentityService in AppEngine SDK.
50  */
51 class AppEngineCredentials extends GoogleCredentials implements ServiceAccountSigner {
52 
53   private static final long serialVersionUID = -493219027336622194L;
54 
55   static final String APP_IDENTITY_SERVICE_FACTORY_CLASS =
56       "com.google.appengine.api.appidentity.AppIdentityServiceFactory";
57   static final String APP_IDENTITY_SERVICE_CLASS =
58       "com.google.appengine.api.appidentity.AppIdentityService";
59   static final String GET_ACCESS_TOKEN_RESULT_CLASS =
60       "com.google.appengine.api.appidentity.AppIdentityService$GetAccessTokenResult";
61   static final String SIGNING_RESULT_CLASS =
62       "com.google.appengine.api.appidentity.AppIdentityService$SigningResult";
63   private static final String GET_APP_IDENTITY_SERVICE_METHOD = "getAppIdentityService";
64   private static final String GET_ACCESS_TOKEN_RESULT_METHOD = "getAccessToken";
65   private static final String GET_ACCESS_TOKEN_METHOD = "getAccessToken";
66   private static final String GET_EXPIRATION_TIME_METHOD = "getExpirationTime";
67   private static final String GET_SERVICE_ACCOUNT_NAME_METHOD = "getServiceAccountName";
68   private static final String SIGN_FOR_APP_METHOD = "signForApp";
69   private static final String GET_SIGNATURE_METHOD = "getSignature";
70 
71   private final Collection<String> scopes;
72   private final boolean scopesRequired;
73 
74   private transient Object appIdentityService;
75   private transient Method getAccessToken;
76   private transient Method getAccessTokenResult;
77   private transient Method getExpirationTime;
78   private transient Method signForApp;
79   private transient Method getSignature;
80   private transient String account;
81 
AppEngineCredentials(Collection<String> scopes, Collection<String> defaultScopes)82   AppEngineCredentials(Collection<String> scopes, Collection<String> defaultScopes)
83       throws IOException {
84     // Use defaultScopes only when scopes don't exist.
85     if (scopes == null || scopes.isEmpty()) {
86       this.scopes =
87           defaultScopes == null ? ImmutableList.<String>of() : ImmutableList.copyOf(defaultScopes);
88     } else {
89       this.scopes = ImmutableList.copyOf(scopes);
90     }
91     this.scopesRequired = this.scopes.isEmpty();
92     init();
93   }
94 
AppEngineCredentials( Collection<String> scopes, Collection<String> defaultScopes, AppEngineCredentials unscoped)95   AppEngineCredentials(
96       Collection<String> scopes, Collection<String> defaultScopes, AppEngineCredentials unscoped) {
97     this.appIdentityService = unscoped.appIdentityService;
98     this.getAccessToken = unscoped.getAccessToken;
99     this.getAccessTokenResult = unscoped.getAccessTokenResult;
100     this.getExpirationTime = unscoped.getExpirationTime;
101     // Use defaultScopes only when scopes don't exist.
102     if (scopes == null || scopes.isEmpty()) {
103       this.scopes =
104           defaultScopes == null ? ImmutableSet.<String>of() : ImmutableList.copyOf(defaultScopes);
105     } else {
106       this.scopes = ImmutableList.copyOf(scopes);
107     }
108     this.scopesRequired = this.scopes.isEmpty();
109   }
110 
init()111   private void init() throws IOException {
112     try {
113       Class<?> factoryClass = forName(APP_IDENTITY_SERVICE_FACTORY_CLASS);
114       Method method = factoryClass.getMethod(GET_APP_IDENTITY_SERVICE_METHOD);
115       this.appIdentityService = method.invoke(null);
116       Class<?> serviceClass = forName(APP_IDENTITY_SERVICE_CLASS);
117       Class<?> tokenResultClass = forName(GET_ACCESS_TOKEN_RESULT_CLASS);
118       this.getAccessTokenResult =
119           serviceClass.getMethod(GET_ACCESS_TOKEN_RESULT_METHOD, Iterable.class);
120       this.getAccessToken = tokenResultClass.getMethod(GET_ACCESS_TOKEN_METHOD);
121       this.getExpirationTime = tokenResultClass.getMethod(GET_EXPIRATION_TIME_METHOD);
122       this.account =
123           (String)
124               serviceClass.getMethod(GET_SERVICE_ACCOUNT_NAME_METHOD).invoke(appIdentityService);
125       this.signForApp = serviceClass.getMethod(SIGN_FOR_APP_METHOD, byte[].class);
126       Class<?> signingResultClass = forName(SIGNING_RESULT_CLASS);
127       this.getSignature = signingResultClass.getMethod(GET_SIGNATURE_METHOD);
128     } catch (ClassNotFoundException
129         | NoSuchMethodException
130         | IllegalAccessException
131         | InvocationTargetException ex) {
132       throw new IOException(
133           "Application Default Credentials failed to create the Google App Engine service account"
134               + " credentials. Check that the App Engine SDK is deployed.",
135           ex);
136     }
137   }
138 
139   /** Refresh the access token by getting it from the App Identity service. */
140   @Override
refreshAccessToken()141   public AccessToken refreshAccessToken() throws IOException {
142     if (createScopedRequired()) {
143       throw new IOException("AppEngineCredentials requires createScoped call before use.");
144     }
145     try {
146       Object accessTokenResult = getAccessTokenResult.invoke(appIdentityService, scopes);
147       String accessToken = (String) getAccessToken.invoke(accessTokenResult);
148       Date expirationTime = (Date) getExpirationTime.invoke(accessTokenResult);
149       return new AccessToken(accessToken, expirationTime);
150     } catch (Exception e) {
151       throw new IOException("Could not get the access token.", e);
152     }
153   }
154 
155   @Override
createScopedRequired()156   public boolean createScopedRequired() {
157     return scopesRequired;
158   }
159 
160   @Override
createScoped(Collection<String> scopes)161   public GoogleCredentials createScoped(Collection<String> scopes) {
162     return new AppEngineCredentials(scopes, null, this);
163   }
164 
165   @Override
createScoped( Collection<String> scopes, Collection<String> defaultScopes)166   public GoogleCredentials createScoped(
167       Collection<String> scopes, Collection<String> defaultScopes) {
168     return new AppEngineCredentials(scopes, defaultScopes, this);
169   }
170 
171   @Override
getAccount()172   public String getAccount() {
173     return account;
174   }
175 
176   @Override
sign(byte[] toSign)177   public byte[] sign(byte[] toSign) {
178     try {
179       Object signingResult = signForApp.invoke(appIdentityService, toSign);
180       return (byte[]) getSignature.invoke(signingResult);
181     } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
182       throw new SigningException("Failed to sign the provided bytes", ex);
183     }
184   }
185 
186   @Override
hashCode()187   public int hashCode() {
188     return Objects.hash(scopes, scopesRequired);
189   }
190 
191   @Override
toString()192   public String toString() {
193     return MoreObjects.toStringHelper(this)
194         .add("scopes", scopes)
195         .add("scopesRequired", scopesRequired)
196         .toString();
197   }
198 
199   @Override
equals(Object obj)200   public boolean equals(Object obj) {
201     if (!(obj instanceof AppEngineCredentials)) {
202       return false;
203     }
204     AppEngineCredentials other = (AppEngineCredentials) obj;
205     return this.scopesRequired == other.scopesRequired && Objects.equals(this.scopes, other.scopes);
206   }
207 
readObject(ObjectInputStream input)208   private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
209     input.defaultReadObject();
210     init();
211   }
212 
213   /*
214    * Start of methods to allow overriding in the test code to isolate from the environment.
215    */
216 
forName(String className)217   Class<?> forName(String className) throws ClassNotFoundException {
218     return Class.forName(className);
219   }
220 
221   /*
222    * End of methods to allow overriding in the test code to isolate from the environment.
223    */
224 }
225