/* * Copyright (C) 2012 Pixmob (http://github.com/pixmob) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.pixmob.httpclient; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URLEncoder; import java.util.Map; import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; /** * This {@link HttpRequestHandler} implementation authenticates requests to a * remote GAE application. * <p> * Using this class requires the permission * <tt>android.permission.USE_CREDENTIALS</tt>. * </p> * @author Pixmob */ public class GoogleAppEngineAuthenticator extends AbstractAccountAuthenticator { public static final String GOOGLE_ACCOUNT_TYPE = "com.google"; private final String gaeHost; private String authCookieValue; /** * Create a new instance of this authenticator. * @param context * application context * @param account * Google account * @param gaeHost * Google App Engine hostname, such as <tt>myapp.appspot.com</tt> */ public GoogleAppEngineAuthenticator(final Context context, final Account account, final String gaeHost) { super(context, account); if (gaeHost == null) { throw new IllegalArgumentException("Google App Engine host is required"); } this.gaeHost = gaeHost; } private String fetchAuthCookie(String authToken, boolean invalidateToken) throws HttpClientException { final AccountManager am = (AccountManager) getContext().getSystemService(Context.ACCOUNT_SERVICE); if (invalidateToken) { // Invalidate authentication token, and generate a new one. am.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, authToken); authToken = generateAuthToken(); } final String loginUrl = "https://" + gaeHost + "/_ah/login?continue=http://localhost/&auth=" + urlEncode(authToken); final HttpClient hc = new HttpClient(getContext()); final HttpResponse resp; try { resp = hc.get(loginUrl).expect(HttpURLConnection.HTTP_MOVED_TEMP).execute(); } catch (HttpClientException e) { throw new HttpClientException("Authentication failed", e); } // The authentication was successful. // Now we need to get the authentication cookie from the response. final Map<String, String> cookies = resp.getCookies(); String authCookie = cookies.get("SACSID"); if (authCookie == null) { if (!invalidateToken) { // Try again with a new authentication token. return fetchAuthCookie(authToken, true); } } if (authCookie == null) { throw new HttpClientException("Authentication failed"); } return authCookie; } private static String urlEncode(String str) { String encoded = str; try { encoded = URLEncoder.encode(str, "UTF-8"); } catch (UnsupportedEncodingException e) { // Unlikely to happen. } return encoded; } private String generateAuthToken() throws HttpClientException { return generateAuthToken("ah"); } @Override public void onRequest(HttpURLConnection conn) throws Exception { if (authCookieValue == null) { final String authToken = generateAuthToken(); final String authCookie = fetchAuthCookie(authToken, false); authCookieValue = "SACSID=" + authCookie; } // The authentication cookie is only stored in memory, in order to // prevent security issues. conn.addRequestProperty("Cookie", authCookieValue); } }