/* * Copyright 2010 Google Inc. * * 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 com.samsung.android.remindme.jsonrpc; import com.samsung.android.remindme.Config; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthenticationException; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.cookie.Cookie; import org.apache.http.impl.cookie.BasicClientCookie; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.app.Activity; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Bundle; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; /** * An Android/App Engine JSON-RPC client, complete with AccountManager-based auth. */ public class AuthenticatedJsonRpcJavaClient extends JsonRpcJavaClient { public static final String APPENGINE_SERVICE_NAME = "ah"; public static final int NEED_AUTH_NOTIFICATION = 1; public static final int NEED_AUTH_INTENT = 2; private Context mContext; private TokenStoreHelper mTokenStoreHelper; private final String mAuthUrlTemplate; public AuthenticatedJsonRpcJavaClient(Context context, String authUrlTemplate, String rpcUrl) { super(rpcUrl); mContext = context; mAuthUrlTemplate = authUrlTemplate; mTokenStoreHelper = new TokenStoreHelper(context); } @SuppressWarnings("serial") public static class RequestedUserAuthenticationException extends Exception {} @SuppressWarnings("serial") public static class InvalidAuthTokenException extends Exception { public InvalidAuthTokenException() { super(); } public InvalidAuthTokenException(String message) { super(message); } } public void blockingAuthenticateAccount(final Account account, final int needAuthAction, boolean forceReauthenticate) throws AuthenticationException, OperationCanceledException, RequestedUserAuthenticationException, InvalidAuthTokenException { String existingToken = mTokenStoreHelper.getToken(account); if (!forceReauthenticate && existingToken != null) { BasicClientCookie c = new BasicClientCookie("ACSID", existingToken); try { c.setDomain(new URI(Config.SERVER_BASE_URL).getHost()); mHttpClient.getCookieStore().addCookie(c); return; } catch (URISyntaxException e) { } } // Get an auth token for this account. AccountManager am = AccountManager.get(mContext); Bundle authBundle = null; String authToken = null; // Block on getting the auth token result. try { authBundle = am.getAuthToken(account, APPENGINE_SERVICE_NAME, needAuthAction == NEED_AUTH_NOTIFICATION, null, null).getResult(); } catch (IOException e) { throw new AuthenticationException("IOException while getting auth token.", e); } catch (AuthenticatorException e) { throw new AuthenticationException("AuthenticatorException while getting auth token.", e); } if (authBundle.containsKey(AccountManager.KEY_INTENT) && needAuthAction == NEED_AUTH_INTENT) { Intent authRequestIntent = (Intent) authBundle.get(AccountManager.KEY_INTENT); mContext.startActivity(authRequestIntent); throw new RequestedUserAuthenticationException(); } else if (authBundle.containsKey(AccountManager.KEY_AUTHTOKEN)) { authToken = authBundle.getString(AccountManager.KEY_AUTHTOKEN); System.out.println(authToken); System.out.println(AccountManager.KEY_AUTHTOKEN); } if (authToken == null) { throw new AuthenticationException("Retrieved auth token was null."); } try { blockingAuthenticateWithToken(account, authToken); } catch (InvalidAuthTokenException e) { am.invalidateAuthToken(account.type, authToken); throw e; } } private void blockingAuthenticateWithToken(Account account, String authToken) throws AuthenticationException, InvalidAuthTokenException { // Promote the given auth token into an App Engine ACSID token. HttpGet httpGet = new HttpGet(String.format(mAuthUrlTemplate, authToken)); String acsidToken = null; try { HttpResponse response = mHttpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() == 403) { throw new InvalidAuthTokenException(); } List<Cookie> cookies = mHttpClient.getCookieStore().getCookies(); for (Cookie cookie : cookies) { if (cookie.getName().equals("ACSID")) { acsidToken = cookie.getValue(); break; } } if (acsidToken == null && response.getStatusLine().getStatusCode() == 500) { // If no ACSID cookie was passed, it usually means the auth token was invalid; throw new InvalidAuthTokenException("ACSID cookie not found in HTTP response: " + response.getStatusLine().toString() + "; assuming invalid auth token."); } mTokenStoreHelper.putToken(account, acsidToken); } catch (ClientProtocolException e) { throw new AuthenticationException("HTTP Protocol error authenticating to App Engine", e); } catch (IOException e) { throw new AuthenticationException("IOException authenticating to App Engine", e); } } public static void ensureHasTokenWithUI(Activity activity, Account account, final EnsureHasTokenWithUICallback callback) { AccountManager am = AccountManager.get(activity); am.getAuthToken(account, APPENGINE_SERVICE_NAME, null, activity, new AccountManagerCallback<Bundle>() { public void run(AccountManagerFuture<Bundle> authBundleFuture) { Bundle authBundle = null; try { authBundle = authBundleFuture.getResult(); } catch (OperationCanceledException e) { callback.onAuthDenied(); return; } catch (AuthenticatorException e) { callback.onError(e); return; } catch (IOException e) { callback.onError(e); return; } if (authBundle.containsKey(AccountManager.KEY_AUTHTOKEN)) { callback.onHasToken((String) authBundle .get(AccountManager.KEY_AUTHTOKEN)); } else { callback.onError(new IllegalStateException( "No auth token available, but operation not canceled.")); } } }, null); } public void invalidateAccountAcsidToken(Account account) { mTokenStoreHelper.invalidateToken(account); } public static interface EnsureHasTokenWithUICallback { public void onAuthDenied(); public void onHasToken(String authToken); public void onError(Throwable e); } /** * This class helps manage stored ACSID tokens. * TODO: use a persistent cookie store instead of this intermediate structure */ private static class TokenStoreHelper extends SQLiteOpenHelper { private static final String TABLE_NAME = "tokens"; private static final String[] ALL_COLUMNS = new String[] { "account", "token" }; TokenStoreHelper(Context context) { super(context, "tokens.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + TABLE_NAME + " (account TEXT UNIQUE, token TEXT);"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); onCreate(db); } public void putToken(Account account, String token) { SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues(); values.put("account", account.name); values.put("token", token); db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); db.close(); } public String getToken(Account account) { SQLiteDatabase db = getReadableDatabase(); Cursor c = db.query(TABLE_NAME, ALL_COLUMNS, "account = ?", new String[]{ account.name }, null, null, null); if (!c.moveToNext()) { c.close(); db.close(); return null; } String token = c.getString(1); c.close(); db.close(); return token; } public void invalidateToken(Account account) { SQLiteDatabase db = getWritableDatabase(); db.delete(TABLE_NAME, "account = ?", new String[]{ account.name }); db.close(); } } }