mobile/android/base/tests/testPasswordEncrypt.java.in
author Geoff Brown <gbrown@mozilla.com>
Tue, 20 Nov 2012 10:41:03 -0700
changeset 113797 30e3ecf54ab207bf9c7bd313b9278ca2a288170c
parent 111440 a63eeede2d15cc6ed00eedb512ae2fecd3e7b522
child 121801 c7c5e6288ef8c4f6ae7a8cc7545ad3fb51ae1b28
permissions -rw-r--r--
Bug 764901 - Disable part of testPasswordEncrypt to resolve intermittent orange; r=wesj

#filter substitution
package @ANDROID_PACKAGE_NAME@.tests;

import @ANDROID_PACKAGE_NAME@.*;
import android.app.Activity;
import android.content.ContentValues;
import android.content.ContentResolver;
import android.database.Cursor;
import android.content.Context;
import android.net.Uri;
import java.io.File;
import java.lang.reflect.Method;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class testPasswordEncrypt extends BaseTest {
    @Override
    protected int getTestType() {
        return TEST_MOCHITEST;
    }

    public void testPasswordEncrypt() {
      Context context = (Context)getActivity();
      ContentResolver cr = context.getContentResolver();
      mAsserter.isnot(cr, null, "Found a content resolver");
      ContentValues cvs = new ContentValues();

      blockForGeckoReady();

      File db = new File(mProfile, "signons.sqlite");
      String dbPath = db.getPath();

      Uri passwordUri;
      try {
          ClassLoader classLoader = getActivity().getClassLoader();
          Class pwds = classLoader.loadClass("org.mozilla.gecko.db.BrowserContract$Passwords");
          Class nss = classLoader.loadClass("org.mozilla.gecko.NSSBridge");
          Class contextClass = classLoader.loadClass("android.content.Context");
          Class stringClass = classLoader.loadClass("java.lang.String");
          Class appshell = classLoader.loadClass("org.mozilla.gecko.GeckoAppShell");

          Method loadNSSLibs = appshell.getMethod("loadNSSLibs", contextClass, stringClass);
          Method decrypt = nss.getMethod("decrypt", contextClass, stringClass, stringClass);
          Method encrypt = nss.getMethod("encrypt", contextClass, stringClass, stringClass);
  
          cvs.put("hostname", "http://www.example.com");
          cvs.put("encryptedUsername", "username");
          cvs.put("encryptedPassword", "password");

          // Attempt to insert into the db
          passwordUri = (Uri)pwds.getField("CONTENT_URI").get(null);
          Uri.Builder builder = passwordUri.buildUpon();
          passwordUri = builder.appendQueryParameter("profilePath", mProfile).build();

          Uri uri = cr.insert(passwordUri, cvs);
          Uri expectedUri = passwordUri.buildUpon().appendPath("1").build();
          mAsserter.is(uri.toString(), expectedUri.toString(), "Insert returned correct uri");

          Cursor list = mActions.querySql(dbPath, "SELECT encryptedUsername FROM moz_logins");
          String resourcePath = getActivity().getApplication().getPackageResourcePath();
          loadNSSLibs.invoke(null, (Context)getActivity(), resourcePath);

          list.moveToFirst();
          String decryptedU = (String)decrypt.invoke(null, context, mProfile, list.getString(0));
          mAsserter.is(decryptedU, "username", "Username was encrypted correctly when inserting");

          list = mActions.querySql(dbPath, "SELECT encryptedPassword, encType FROM moz_logins");
          list.moveToFirst();
          String decryptedP = (String)decrypt.invoke(null, context, mProfile, list.getString(0));
          mAsserter.is(decryptedP, "password", "Password was encrypted correctly when inserting");
          mAsserter.is(list.getInt(1), 1, "Password has correct encryption type");
  
          cvs.put("encryptedUsername", "username2");
          cvs.put("encryptedPassword", "password2");
          cr.update(passwordUri, cvs, null, null);

          list = mActions.querySql(dbPath, "SELECT encryptedUsername FROM moz_logins");
          list.moveToFirst();
          decryptedU = (String)decrypt.invoke(null, context, mProfile, list.getString(0));
          mAsserter.is(decryptedU, "username2", "Username was encrypted when updating");

          list = mActions.querySql(dbPath, "SELECT encryptedPassword FROM moz_logins");
          list.moveToFirst();
          decryptedP = (String)decrypt.invoke(null, context, mProfile, list.getString(0));
          mAsserter.is(decryptedP, "password2", "Password was encrypted when updating");

          // Trying to store a password while master password is enabled should throw,
          // but because Android can't send Exceptions across processes
          // it just results in a null uri/cursor being returned.
          toggleMasterPassword("password");
          try {
              uri = cr.insert(passwordUri, cvs);
              // TODO: restore this assertion -- see bug 764901
              // mAsserter.is(uri, null, "Storing a password while MP was set should fail");

              Cursor c = cr.query(passwordUri, null, null, null, null);
              // TODO: restore this assertion -- see bug 764901
              // mAsserter.is(c, null, "Querying passwords while MP was set should fail");
          } catch (Exception ex) {
              // Password provider currently can not throw across process
              // so we should not catch this exception here
              mAsserter.ok(false, "Caught exception", ex.toString());
          }
          toggleMasterPassword("password");

      } catch(ClassNotFoundException ex) {
          mAsserter.ok(false, "Error getting class", ex.toString());
          return;
      } catch(NoSuchFieldException ex) {
          mAsserter.ok(false, "Error getting field", ex.toString());
          return;
      } catch(IllegalAccessException ex) {
          mAsserter.ok(false, "Error using field", ex.toString());
          return;
      } catch(java.lang.NoSuchMethodException ex) {
          mAsserter.ok(false, "Error getting method", ex.toString());
          return;
      } catch(java.lang.reflect.InvocationTargetException ex) {
          mAsserter.ok(false, "Error invoking method", ex.toString());
          return;
      }
    }

    private void toggleMasterPassword(String passwd) {
        JSONObject jsonPref = new JSONObject();
        try {
            jsonPref.put("name", "privacy.masterpassword.enabled");
            jsonPref.put("type", "string");
            jsonPref.put("value", passwd);
            mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());

            // Wait for confirmation of the pref change before proceeding with the test.
            JSONArray getPrefData = new JSONArray();
            getPrefData.put("privacy.masterpassword.enabled");
            JSONObject message = new JSONObject();
            message.put("requestId", "testPasswordEncrypt");
            message.put("preferences", getPrefData);
            Actions.RepeatedEventExpecter contentEventExpecter = mActions.expectGeckoEvent("Preferences:Data");
            mActions.sendGeckoEvent("Preferences:Get", message.toString());
            // Receiving a Preferences:Data event is not conclusive evidence that *our*
            // preference has been set -- another component may be changing preferences
            // at the same time. Mitigate this risk by waiting for a Preference:Data
            // and then waiting for a period of time in which no more Preference:Data
            // events are received.
            // TODO: add a new expectGeckoEvent function that listens for a Preferences:Data
            // message with a specific requestId
            contentEventExpecter.blockUntilClear(2000);
        } catch (Exception ex) { 
            mAsserter.ok(false, "exception in toggleMasterPassword", ex.toString());
        }
    }

    public void tearDown() throws Exception {
        // remove the entire signons.sqlite file
        File profile = new File(mProfile);
        File db = new File(profile, "signons.sqlite");
        if (db.delete()) {
            mAsserter.dumpLog("tearDown deleted "+db.toString());
        } else {
            mAsserter.dumpLog("tearDown did not delete "+db.toString());
        }

        super.tearDown();
    }
}