Java tutorial
// Copyright 2015 The Vanadium Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package io.v.android.apps.account_manager; import android.accounts.Account; import android.accounts.AccountAuthenticatorActivity; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.app.ProgressDialog; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Base64; import android.widget.Toast; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.io.CharStreams; import com.google.common.util.concurrent.ListenableFuture; import org.joda.time.Duration; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.security.interfaces.ECPublicKey; import io.v.android.v23.V; import io.v.v23.VFutures; import io.v.v23.context.VContext; import io.v.v23.security.BlessingPattern; import io.v.v23.security.BlessingStore; import io.v.v23.security.Blessings; import io.v.v23.security.Caveat; import io.v.v23.security.CryptoUtil; import io.v.v23.security.VPrincipal; import io.v.v23.security.VSecurity; import io.v.v23.verror.VException; import io.v.x.ref.services.identity.OAuthBlesserClient; import io.v.x.ref.services.identity.OAuthBlesserClientFactory; /** * Creates a new Vanadium account, using the Google accounts present on the * device. */ // TODO: Change to BlessingCreationActivity. public class AccountActivity extends AccountAuthenticatorActivity { public static final String TAG = "AccountActivity"; private static final int REQUEST_CODE_PICK_ACCOUNTS = 1000; private static final int REQUEST_CODE_USER_APPROVAL = 1001; private static final String OAUTH_PROFILE = "https://www.googleapis.com/auth/userinfo.email"; private static final String OAUTH_SCOPE = "oauth2:" + OAUTH_PROFILE; public static final String GOOGLE_ACCOUNT = "GOOGLE_ACCOUNT"; private VContext mBaseContext = null; private String mAccountName = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_account); mBaseContext = V.init(this); Intent intent = getIntent(); if (intent == null) { replyWithError("Intent not found."); return; } // See if the caller wants to use a specific Google account to create the Vanadium account. // If null or empty string is passed, the user will be prompted to choose the Google // account to use. mAccountName = intent.getStringExtra(GOOGLE_ACCOUNT); if (mAccountName != null && !mAccountName.isEmpty()) { getIdentity(); return; } Intent chooseIntent = AccountManager.newChooseAccountIntent(null, null, new String[] { "com.google" }, false, null, null, null, null); startActivityForResult(chooseIntent, REQUEST_CODE_PICK_ACCOUNTS); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_PICK_ACCOUNTS) { if (resultCode != RESULT_OK) { replyWithError("User didn't pick account."); return; } mAccountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); getIdentity(); } else if (requestCode == REQUEST_CODE_USER_APPROVAL) { if (resultCode != RESULT_OK) { replyWithError("User didn't give proposed permissions."); return; } getIdentity(); } super.onActivityResult(requestCode, resultCode, data); } private void getIdentity() { if (mAccountName == null || mAccountName.isEmpty()) { replyWithError("Empty account name."); return; } Account[] accounts = AccountManager.get(this).getAccountsByType("com.google"); Account account = null; for (int i = 0; i < accounts.length; i++) { if (accounts[i].name.equals(mAccountName)) { account = accounts[i]; } } if (account == null) { replyWithError("Couldn't find Google account with name: " + mAccountName); return; } AccountManager.get(this).getAuthToken(account, OAUTH_SCOPE, new Bundle(), false, new OnTokenAcquired(), new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { replyWithError("Error getting auth token: " + msg.toString()); return true; } })); } class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { try { Bundle bundle = result.getResult(); Intent launch = (Intent) bundle.get(AccountManager.KEY_INTENT); if (launch != null) { // Needs user approval. // NOTE(spetrovic): The returned intent has the wrong flag value // FLAG_ACTIVITY_NEW_TASK set, which results in the launched intent replying // immediately with RESULT_CANCELED. Hence, we clear the flag here. launch.setFlags(0); startActivityForResult(launch, REQUEST_CODE_USER_APPROVAL); return; } String token = bundle.getString(AccountManager.KEY_AUTHTOKEN); (new BlessingFetcher()).execute(token); } catch (AuthenticatorException e) { replyWithError("Couldn't authorize: " + e.getMessage()); } catch (OperationCanceledException e) { replyWithError("Authorization cancelled: " + e.getMessage()); } catch (IOException e) { replyWithError("Unexpected error: " + e.getMessage()); } } } private class BlessingFetcher extends AsyncTask<String, Void, Blessings> { ProgressDialog progressDialog = new ProgressDialog(AccountActivity.this); String errorMsg = null; @Override protected void onPreExecute() { progressDialog.setMessage("Fetching Vanadium Identity..."); progressDialog.show(); } @Override protected Blessings doInBackground(String... tokens) { if (tokens.length != 1) { errorMsg = "Empty OAuth token."; return null; } try { URL url = new URL("https://dev.v.io/auth/blessing-root"); JSONObject object = new JSONObject(CharStreams .toString(new InputStreamReader(url.openConnection().getInputStream(), Charsets.US_ASCII))); String publicKey = object.get("publicKey").toString(); byte[] base64DecodedKey = Base64.decode(publicKey.getBytes(), Base64.URL_SAFE); ECPublicKey ecPublicKey = CryptoUtil.decodeECPublicKey(base64DecodedKey); JSONArray namesArray = (JSONArray) object.get("names"); for (int i = 0; i < namesArray.length(); i++) { String name = namesArray.getString(i); V.getPrincipal(mBaseContext).roots().add(ecPublicKey, new BlessingPattern(name)); } OAuthBlesserClient blesser = OAuthBlesserClientFactory .getOAuthBlesserClient(Constants.IDENTITY_DEV_V_IO_U_GOOGLE); VContext ctx = mBaseContext.withTimeout(new Duration(20000)); // 20s ListenableFuture<OAuthBlesserClient.BlessUsingAccessTokenWithCaveatsOut> reply = blesser .blessUsingAccessTokenWithCaveats(ctx, tokens[0], ImmutableList.<Caveat>of(VSecurity.newUnconstrainedUseCaveat())); Blessings blessing = VFutures.sync(reply).blessing; if (blessing == null || blessing.getCertificateChains() == null || blessing.getCertificateChains().size() <= 0) { errorMsg = "Received empty blessing from Vanadium identity servers."; return null; } if (blessing.getCertificateChains().size() > 1) { errorMsg = "Received more than one blessing from Vanadium identity servers."; return null; } return blessing; } catch (VException e) { errorMsg = e.getMessage(); return null; } catch (MalformedURLException e) { errorMsg = e.getMessage(); return null; } catch (JSONException e) { errorMsg = e.getMessage(); return null; } catch (IOException e) { errorMsg = e.getMessage(); return null; } } @Override protected void onPostExecute(Blessings blessing) { progressDialog.dismiss(); if (blessing == null) { // Indicates an error replyWithError("Couldn't get identity from Vanadium identity servers: " + errorMsg); return; } replyWithSuccess(blessing); } } private void replyWithError(String error) { android.util.Log.e(TAG, "Error creating account: " + error); setResult(RESULT_CANCELED); String text = "Couldn't create blessing: " + error; Toast.makeText(this, text, Toast.LENGTH_LONG).show(); finish(); } private void replyWithSuccess(Blessings blessing) { enforceAccountExists(); // Store the obtained blessing from identity server. try { VPrincipal principal = V.getPrincipal(mBaseContext); BlessingStore blessingStore = principal.blessingStore(); blessingStore.set(blessing, new BlessingPattern(blessing.toString())); } catch (VException e) { replyWithError("Couldn't store obtained blessing: " + e.getMessage()); } setResult(RESULT_OK); Toast.makeText(this, "Success.", Toast.LENGTH_SHORT).show(); finish(); } private void enforceAccountExists() { if (AccountManager.get(this).getAccountsByType(Constants.ACCOUNT_TYPE).length <= 0) { String name = "Vanadium"; Account account = new Account(name, getResources().getString(R.string.authenticator_account_type)); AccountManager am = AccountManager.get(this); am.addAccountExplicitly(account, null, null); } } }