1.9.30 sync.
[gae.git] / java / src / main / com / google / appengine / tools / admin / ClientLoginServerConnection.java
blobcb584d1ad1b855afd15b36ca7305f0a48dba20b3
1 // Copyright 2011 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.admin;
5 import com.google.appengine.tools.admin.AppAdminFactory.ConnectOptions;
6 import com.google.appengine.tools.util.ClientCookie;
7 import com.google.appengine.tools.util.ClientCookieManager;
8 import com.google.common.annotations.VisibleForTesting;
10 import java.io.BufferedReader;
11 import java.io.ByteArrayOutputStream;
12 import java.io.IOException;
13 import java.io.ObjectOutputStream;
14 import java.net.HttpURLConnection;
15 import java.net.URL;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.prefs.Preferences;
22 /**
24 public class ClientLoginServerConnection extends AbstractServerConnection {
26 /**
27 * Exception type for email/password mismatch failures.
29 public class ClientAuthFailException extends ClientLoginException {
30 public ClientAuthFailException(String s) {
31 super(s);
34 public ClientAuthFailException(String s, Throwable t) {
35 super(s, t);
38 /**
39 * Exception type for login failures.
41 public class ClientLoginException extends IOException {
42 public ClientLoginException(String s) {
43 super(s);
46 public ClientLoginException(String s, Throwable t) {
47 super(s, t);
51 protected ClientCookieManager cookies;
53 public ClientLoginServerConnection(ConnectOptions options) {
54 super(options);
55 cookies = options.getCookies();
56 if (cookies == null) {
57 cookies = new ClientCookieManager();
61 /**
62 * Authenticates the user.
64 * The authentication process works as follows: 1) We get a username and
65 * password from the user 2) We use ClientLogin to obtain an AUTH token for
66 * the user 3) We pass the auth token to /_ah/login on the server to obtain an
67 * authentication cookie. If login was successful, it tries to redirect us to
68 * the URL we provided.
70 * If we attempt to access the upload API without first obtaining an
71 * authentication cookie, it returns a 401 response and directs us to
72 * authenticate ourselves with ClientLogin.
74 * @param url which require authentication
75 * @param account_type
76 * @see <a
77 * href="http://code.google.com/apis/accounts/AuthForInstalledApps.html"></a>
79 private void authenticate(URL url, String account_type) throws ClientLoginException,
80 IOException {
81 for (int unused = 1;; ++unused) {
82 try {
83 String authToken = getAuthToken(url.getHost(), account_type);
84 getAuthCookie(authToken);
85 break;
86 } catch (ClientLoginException e) {
87 if (unused >= 3) {
88 throw e;
92 checkAuthCookie(url);
95 @VisibleForTesting
96 void checkAuthCookie(URL url) throws ClientLoginException {
97 long minExpireTime = System.currentTimeMillis() + 60 * 1000;
98 Iterator<ClientCookie> li = cookies.getCookies();
99 while (li.hasNext()) {
100 final ClientCookie cookie = li.next();
101 if (cookie.getExpirationTime() > minExpireTime && cookie.match(url)) {
102 return;
106 throw new ClientLoginException("This system's clock appears to be set incorrectly. "+
107 "Check the system time and set it to the correct time " +
108 "before trying again.");
111 @Override
112 protected void doHandleSendErrors(int status, URL url, HttpURLConnection conn,
113 BufferedReader connReader) throws IOException {
114 if (status == 401) {
115 authenticate(url, null);
116 } else if (status == 403) {
117 System.out.println(constructHttpErrorMessage(conn, connReader));
118 authenticate(url, null);
119 } else if (status >= 500 && status <= 600) {
120 } else if (status == 302) {
121 Map<String, List<String>> headers = conn.getHeaderFields();
122 String location = headers.get("Location").get(0);
123 if (location.startsWith("https://www.google.com/accounts/ServiceLogin")) {
124 authenticate(url, null);
125 } else if (location.matches("https://www.google.com/a/[a-z0-9.-]+/ServiceLogin.*")) {
126 authenticate(url, "HOSTED");
131 @Override
132 protected void doPostConnect(String method, HttpURLConnection conn, DataPoster data)
133 throws IOException {
134 cookies.readCookies(conn);
135 if (options.getUsePersistedCredentials()) {
136 saveCookies();
140 @Override
141 protected void doPreConnect(String method, HttpURLConnection conn, DataPoster data) {
142 cookies.writeCookies(conn);
145 private void getAuthCookie(String token) throws IOException {
146 Map<String, String> params = new HashMap<String, String>();
147 params.put("continue", "http://localhost/");
148 params.put("auth", token);
149 String query = buildQuery(params);
150 URL url = buildURL("/_ah/login?" + query);
151 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
152 if (options.getHost() != null) {
153 conn.setRequestProperty("Host", options.getHost());
156 IOException ioe = connect(POST, conn, null);
157 if (conn.getResponseCode() != 302
158 || !"http://localhost/".equals(conn.getHeaderField("Location"))) {
159 throw new RuntimeException("Bad authentication response: " + conn.getResponseCode() + " "
160 + conn.getResponseMessage());
161 } else if (ioe != null) {
162 throw ioe;
167 private String getAuthToken(String host, String accountType) throws IOException,
168 ClientLoginException {
169 if (accountType == null) {
170 if (host.endsWith(".google.com")) {
171 accountType = "HOSTED_OR_GOOGLE";
172 } else if (options.getHost() != null && options.getHost().endsWith(".google.com")) {
173 accountType = "HOSTED_OR_GOOGLE";
174 } else {
175 accountType = "GOOGLE";
178 URL url = new URL("https://www.google.com/accounts/ClientLogin");
179 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
181 String password = options.getPasswordPrompt().getPassword();
182 Map<String, String> params = new HashMap<String, String>();
183 params.put("Email", options.getUserId());
184 params.put("Passwd", password);
185 params.put("service", "ah");
186 params.put("source", "Google-appcfg-java-unknown");
187 params.put("accountType", accountType);
188 IOException ioe = connect(POST, conn, new StringPoster(buildQuery(params)));
190 BufferedReader reader = getReader(conn);
191 HashMap<String, String> response = new HashMap<String, String>();
192 String line = null;
193 while ((line = reader.readLine()) != null) {
194 String[] pair = line.split("=", 2);
195 if (pair.length == 2) {
196 response.put(pair[0], pair[1]);
200 if (conn.getResponseCode() == 200) {
201 return response.get("Auth");
202 } else if (conn.getResponseCode() == 403) {
203 String reason = response.get("Error");
204 if ("BadAuthentication".equals(reason)) {
205 String info = response.get("Info");
206 if ("InvalidSecondFactor".equals(info)) {
207 throw new ClientLoginException(""
208 + "Use an application-specific password instead of your regular account password. "
209 + "See http://www.google.com/support/accounts/answer/185833",
210 ioe);
212 else {
213 throw new ClientLoginException("Email \"" + options.getUserId()
214 + "\" and password do not match.", ioe);
216 } else if ("CaptchaRequired".equals(reason)) {
217 throw new ClientLoginException("Please go to "
218 + "https://www.google.com/accounts/DisplayUnlockCaptcha "
219 + "and verify you are a human. Then try again.");
220 } else if ("NotVerified".equals(reason)) {
221 throw new ClientLoginException("Your account has not yet been "
222 + "verfied. Please check your email to do that, then try again.");
223 } else if ("TermsNotAgreed".equals(reason)) {
224 throw new ClientLoginException("You have not yet agreed to the "
225 + "Terms of Service on your account. Please do that, then try again.");
226 } else if ("AccountDeleted".equals(reason)) {
227 throw new ClientLoginException("Your user account has been deleted."
228 + " If this is an error, contact account support at "
229 + "http://www.google.com/support/accounts/");
230 } else if ("AccountDisabled".equals(reason)) {
231 throw new ClientLoginException("Your user account has been disabled."
232 + " If this is an error, contact account support at "
233 + "http://www.google.com/support/accounts/");
234 } else if ("ServiceUnavailable".equals(reason)) {
235 throw new ClientLoginException("The service is currently "
236 + "unavailable; try again later.");
238 throw new ClientLoginException(response.get("Error"), ioe);
239 } else if (conn.getResponseCode() == 401) {
240 throw new ClientAuthFailException("Email \"" + options.getUserId()
241 + "\" and password do not match.", ioe);
242 } else {
243 throw new RuntimeException("Bad authentication response: " + conn.getResponseCode() + " "
244 + conn.getResponseMessage(), ioe);
248 public void saveCookies() throws IOException {
249 if (options.getUserId() == null) {
250 return;
252 ByteArrayOutputStream out = new ByteArrayOutputStream();
253 new ObjectOutputStream(out).writeObject(cookies);
254 byte[] bytes = out.toByteArray();
255 Preferences prefs = Preferences.userNodeForPackage(ServerConnection.class);
256 prefs.put("email", options.getUserId());
257 prefs.putByteArray("cookies", bytes);