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.JsonParser; 35 import com.google.common.base.Charsets; 36 import com.google.common.base.MoreObjects; 37 import com.google.errorprone.annotations.CanIgnoreReturnValue; 38 import java.io.BufferedReader; 39 import java.io.IOException; 40 import java.io.InputStreamReader; 41 import java.io.OutputStream; 42 import java.net.Socket; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Objects; 46 47 /** OAuth2 credentials representing the built-in service account for Google Cloud Shell. */ 48 public class CloudShellCredentials extends GoogleCredentials { 49 50 private static final long serialVersionUID = -2133257318957488451L; 51 private static final int ACCESS_TOKEN_INDEX = 2; 52 private static final int READ_TIMEOUT_MS = 5000; 53 54 /** 55 * The Cloud Shell back authorization channel uses serialized Javascript Protobuffers, preceded by 56 * the message length and a new line character. However, the request message has no content, so a 57 * token request consists of an empty JsPb, and its 2 character length prefix. 58 */ 59 protected static final String GET_AUTH_TOKEN_REQUEST = "2\n[]"; 60 61 protected static final byte[] GET_AUTH_TOKEN_REQUEST_BYTES = 62 (GET_AUTH_TOKEN_REQUEST + "\n").getBytes(Charsets.UTF_8); 63 64 private final int authPort; 65 create(int authPort)66 public static CloudShellCredentials create(int authPort) { 67 return CloudShellCredentials.newBuilder().setAuthPort(authPort).build(); 68 } 69 CloudShellCredentials(Builder builder)70 private CloudShellCredentials(Builder builder) { 71 super(builder); 72 this.authPort = builder.getAuthPort(); 73 } 74 getAuthPort()75 protected int getAuthPort() { 76 return this.authPort; 77 } 78 79 @Override refreshAccessToken()80 public AccessToken refreshAccessToken() throws IOException { 81 Socket socket = new Socket("localhost", this.getAuthPort()); 82 socket.setSoTimeout(READ_TIMEOUT_MS); 83 AccessToken token; 84 try { 85 OutputStream os = socket.getOutputStream(); 86 os.write(GET_AUTH_TOKEN_REQUEST_BYTES); 87 88 BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); 89 input.readLine(); // Skip over the first line 90 JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(input); 91 List<Object> messageArray = (List<Object>) parser.parseArray(ArrayList.class, Object.class); 92 String accessToken = messageArray.get(ACCESS_TOKEN_INDEX).toString(); 93 token = new AccessToken(accessToken, null); 94 } finally { 95 socket.close(); 96 } 97 return token; 98 } 99 100 @Override hashCode()101 public int hashCode() { 102 return Objects.hash(authPort); 103 } 104 105 @Override toString()106 public String toString() { 107 return MoreObjects.toStringHelper(this).add("authPort", authPort).toString(); 108 } 109 110 @Override equals(Object obj)111 public boolean equals(Object obj) { 112 if (!(obj instanceof CloudShellCredentials)) { 113 return false; 114 } 115 CloudShellCredentials other = (CloudShellCredentials) obj; 116 return this.authPort == other.authPort; 117 } 118 119 @Override toBuilder()120 public Builder toBuilder() { 121 return new Builder(this); 122 } 123 newBuilder()124 public static Builder newBuilder() { 125 return new Builder(); 126 } 127 128 public static class Builder extends GoogleCredentials.Builder { 129 private int authPort; 130 Builder()131 protected Builder() {} 132 Builder(CloudShellCredentials credentials)133 protected Builder(CloudShellCredentials credentials) { 134 this.authPort = credentials.authPort; 135 } 136 137 @CanIgnoreReturnValue setAuthPort(int authPort)138 public Builder setAuthPort(int authPort) { 139 this.authPort = authPort; 140 return this; 141 } 142 143 @CanIgnoreReturnValue setQuotaProjectId(String quotaProjectId)144 public Builder setQuotaProjectId(String quotaProjectId) { 145 super.quotaProjectId = quotaProjectId; 146 return this; 147 } 148 getAuthPort()149 public int getAuthPort() { 150 return authPort; 151 } 152 153 @Override build()154 public CloudShellCredentials build() { 155 return new CloudShellCredentials(this); 156 } 157 } 158 } 159