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.http.LowLevelHttpRequest;
35 import com.google.api.client.http.LowLevelHttpResponse;
36 import com.google.api.client.json.GenericJson;
37 import com.google.api.client.json.Json;
38 import com.google.api.client.testing.http.MockHttpTransport;
39 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
40 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
41 import com.google.common.io.BaseEncoding;
42 import java.io.IOException;
43 import java.io.UnsupportedEncodingException;
44 import java.net.MalformedURLException;
45 import java.net.URL;
46 import java.net.URLDecoder;
47 import java.util.HashMap;
48 import java.util.Map;
49 
50 /** Transport that simulates the GCE metadata server for access tokens. */
51 public class MockMetadataServerTransport extends MockHttpTransport {
52 
53   private String accessToken;
54 
55   private Integer requestStatusCode;
56 
57   private String serviceAccountEmail;
58 
59   private String idToken;
60 
61   private byte[] signature;
62 
MockMetadataServerTransport()63   public MockMetadataServerTransport() {}
64 
setAccessToken(String accessToken)65   public void setAccessToken(String accessToken) {
66     this.accessToken = accessToken;
67   }
68 
setRequestStatusCode(Integer requestStatusCode)69   public void setRequestStatusCode(Integer requestStatusCode) {
70     this.requestStatusCode = requestStatusCode;
71   }
72 
setServiceAccountEmail(String serviceAccountEmail)73   public void setServiceAccountEmail(String serviceAccountEmail) {
74     this.serviceAccountEmail = serviceAccountEmail;
75   }
76 
setSignature(byte[] signature)77   public void setSignature(byte[] signature) {
78     this.signature = signature;
79   }
80 
setIdToken(String idToken)81   public void setIdToken(String idToken) {
82     this.idToken = idToken;
83   }
84 
85   @Override
buildRequest(String method, String url)86   public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
87     if (url.equals(ComputeEngineCredentials.getTokenServerEncodedUrl())) {
88       return getMockRequestForTokenEndpoint(url);
89     } else if (isGetServiceAccountsUrl(url)) {
90       return getMockRequestForServiceAccount(url);
91     } else if (isSignRequestUrl(url)) {
92       return getMockRequestForSign(url);
93     } else if (isIdentityDocumentUrl(url)) {
94       return getMockRequestForIdentityDocument(url);
95     }
96     return new MockLowLevelHttpRequest(url) {
97       @Override
98       public LowLevelHttpResponse execute() {
99         if (requestStatusCode != null) {
100           return new MockLowLevelHttpResponse()
101               .setStatusCode(requestStatusCode)
102               .setContent("Metadata Error");
103         }
104 
105         MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
106         response.addHeader("Metadata-Flavor", "Google");
107         return response;
108       }
109     };
110   }
111 
112   private MockLowLevelHttpRequest getMockRequestForSign(String url) {
113     return new MockLowLevelHttpRequest(url) {
114       @Override
115       public LowLevelHttpResponse execute() throws IOException {
116         // Create the JSON response
117         GenericJson signContents = new GenericJson();
118         signContents.setFactory(OAuth2Utils.JSON_FACTORY);
119         signContents.put("signedBlob", BaseEncoding.base64().encode(signature));
120 
121         String signature = signContents.toPrettyString();
122 
123         return new MockLowLevelHttpResponse().setContentType(Json.MEDIA_TYPE).setContent(signature);
124       }
125     };
126   }
127 
128   private MockLowLevelHttpRequest getMockRequestForServiceAccount(String url) {
129     return new MockLowLevelHttpRequest(url) {
130       @Override
131       public LowLevelHttpResponse execute() throws IOException {
132         // Create the JSON response
133         GenericJson serviceAccountsContents = new GenericJson();
134         serviceAccountsContents.setFactory(OAuth2Utils.JSON_FACTORY);
135         GenericJson defaultAccount = new GenericJson();
136         defaultAccount.put("email", serviceAccountEmail);
137         serviceAccountsContents.put("default", defaultAccount);
138 
139         String serviceAccounts = serviceAccountsContents.toPrettyString();
140 
141         return new MockLowLevelHttpResponse()
142             .setContentType(Json.MEDIA_TYPE)
143             .setContent(serviceAccounts);
144       }
145     };
146   }
147 
148   private MockLowLevelHttpRequest getMockRequestForTokenEndpoint(String url) {
149     return new MockLowLevelHttpRequest(url) {
150       @Override
151       public LowLevelHttpResponse execute() throws IOException {
152 
153         if (requestStatusCode != null) {
154           return new MockLowLevelHttpResponse()
155               .setStatusCode(requestStatusCode)
156               .setContent("Token Fetch Error");
157         }
158 
159         String metadataRequestHeader = getFirstHeaderValue("Metadata-Flavor");
160         if (!"Google".equals(metadataRequestHeader)) {
161           throw new IOException("Metadata request header not found.");
162         }
163 
164         // Create the JSON response
165         GenericJson refreshContents = new GenericJson();
166         refreshContents.setFactory(OAuth2Utils.JSON_FACTORY);
167         refreshContents.put("access_token", accessToken);
168         refreshContents.put("expires_in", 3600000);
169         refreshContents.put("token_type", "Bearer");
170         String refreshText = refreshContents.toPrettyString();
171 
172         return new MockLowLevelHttpResponse()
173             .setContentType(Json.MEDIA_TYPE)
174             .setContent(refreshText);
175       }
176     };
177   }
178 
179   private MockLowLevelHttpRequest getMockRequestForIdentityDocument(String url)
180       throws MalformedURLException, UnsupportedEncodingException {
181     if (idToken != null) {
182       return new MockLowLevelHttpRequest(url) {
183         @Override
184         public LowLevelHttpResponse execute() throws IOException {
185           return new MockLowLevelHttpResponse().setContent(idToken);
186         }
187       };
188     }
189 
190     // https://cloud.google.com/compute/docs/instances/verifying-instance-identity#token_format
191     Map<String, String> queryPairs = new HashMap<String, String>();
192     String query = (new URL(url)).getQuery();
193     String[] pairs = query.split("&");
194     for (String pair : pairs) {
195       int idx = pair.indexOf("=");
196       queryPairs.put(
197           URLDecoder.decode(pair.substring(0, idx), "UTF-8"),
198           URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
199     }
200 
201     if (queryPairs.containsKey("format")) {
202       if (((String) queryPairs.get("format")).equals("full")) {
203 
204         // return license only if format=full is set
205         if (queryPairs.containsKey("license")) {
206           if (((String) queryPairs.get("license")).equals("TRUE")) {
207             return new MockLowLevelHttpRequest(url) {
208               @Override
209               public LowLevelHttpResponse execute() throws IOException {
210                 return new MockLowLevelHttpResponse()
211                     .setContent(ComputeEngineCredentialsTest.FULL_ID_TOKEN_WITH_LICENSE);
212               }
213             };
214           }
215         }
216         // otherwise return full format
217         return new MockLowLevelHttpRequest(url) {
218           @Override
219           public LowLevelHttpResponse execute() throws IOException {
220             return new MockLowLevelHttpResponse()
221                 .setContent(ComputeEngineCredentialsTest.FULL_ID_TOKEN);
222           }
223         };
224       }
225     }
226     // Return default format if nothing is set
227     return new MockLowLevelHttpRequest(url) {
228       @Override
229       public LowLevelHttpResponse execute() throws IOException {
230         return new MockLowLevelHttpResponse()
231             .setContent(ComputeEngineCredentialsTest.STANDARD_ID_TOKEN);
232       }
233     };
234   }
235 
236   protected boolean isGetServiceAccountsUrl(String url) {
237     return url.equals(ComputeEngineCredentials.getServiceAccountsUrl());
238   }
239 
240   protected boolean isSignRequestUrl(String url) {
241     return serviceAccountEmail != null
242         && url.equals(
243             String.format(ComputeEngineCredentials.SIGN_BLOB_URL_FORMAT, serviceAccountEmail));
244   }
245 
246   protected boolean isIdentityDocumentUrl(String url) {
247     return url.startsWith(String.format(ComputeEngineCredentials.getIdentityDocumentUrl()));
248   }
249 }
250