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