michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.db; michael@0: michael@0: import java.util.HashMap; michael@0: michael@0: import org.mozilla.gecko.GeckoApp; michael@0: import org.mozilla.gecko.GeckoAppShell; michael@0: import org.mozilla.gecko.GeckoEvent; michael@0: import org.mozilla.gecko.NSSBridge; michael@0: import org.mozilla.gecko.db.BrowserContract.DeletedPasswords; michael@0: import org.mozilla.gecko.db.BrowserContract.Passwords; michael@0: import org.mozilla.gecko.mozglue.GeckoLoader; michael@0: import org.mozilla.gecko.sqlite.MatrixBlobCursor; michael@0: import org.mozilla.gecko.sqlite.SQLiteBridge; michael@0: import org.mozilla.gecko.sync.Utils; michael@0: michael@0: import android.content.ContentValues; michael@0: import android.content.Intent; michael@0: import android.content.UriMatcher; michael@0: import android.database.Cursor; michael@0: import android.net.Uri; michael@0: import android.text.TextUtils; michael@0: import android.util.Log; michael@0: michael@0: public class PasswordsProvider extends SQLiteBridgeContentProvider { michael@0: static final String TABLE_PASSWORDS = "moz_logins"; michael@0: static final String TABLE_DELETED_PASSWORDS = "moz_deleted_logins"; michael@0: michael@0: private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_PASSWORDS"; michael@0: michael@0: private static final int PASSWORDS = 100; michael@0: private static final int DELETED_PASSWORDS = 101; michael@0: michael@0: static final String DEFAULT_PASSWORDS_SORT_ORDER = Passwords.HOSTNAME + " ASC"; michael@0: static final String DEFAULT_DELETED_PASSWORDS_SORT_ORDER = DeletedPasswords.TIME_DELETED + " ASC"; michael@0: michael@0: private static final UriMatcher URI_MATCHER; michael@0: michael@0: private static HashMap PASSWORDS_PROJECTION_MAP; michael@0: private static HashMap DELETED_PASSWORDS_PROJECTION_MAP; michael@0: michael@0: // this should be kept in sync with the version in toolkit/components/passwordmgr/storage-mozStorage.js michael@0: private static final int DB_VERSION = 5; michael@0: private static final String DB_FILENAME = "signons.sqlite"; michael@0: private static final String WHERE_GUID_IS_NULL = BrowserContract.DeletedPasswords.GUID + " IS NULL"; michael@0: private static final String WHERE_GUID_IS_VALUE = BrowserContract.DeletedPasswords.GUID + " = ?"; michael@0: michael@0: private static final String LOG_TAG = "GeckPasswordsProvider"; michael@0: michael@0: static { michael@0: URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); michael@0: michael@0: // content://org.mozilla.gecko.providers.browser/passwords/# michael@0: URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "passwords", PASSWORDS); michael@0: michael@0: PASSWORDS_PROJECTION_MAP = new HashMap(); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.ID, Passwords.ID); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.HOSTNAME, Passwords.HOSTNAME); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.HTTP_REALM, Passwords.HTTP_REALM); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.FORM_SUBMIT_URL, Passwords.FORM_SUBMIT_URL); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.USERNAME_FIELD, Passwords.USERNAME_FIELD); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.PASSWORD_FIELD, Passwords.PASSWORD_FIELD); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.ENCRYPTED_USERNAME, Passwords.ENCRYPTED_USERNAME); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.ENCRYPTED_PASSWORD, Passwords.ENCRYPTED_PASSWORD); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.GUID, Passwords.GUID); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.ENC_TYPE, Passwords.ENC_TYPE); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_CREATED, Passwords.TIME_CREATED); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_LAST_USED, Passwords.TIME_LAST_USED); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_PASSWORD_CHANGED, Passwords.TIME_PASSWORD_CHANGED); michael@0: PASSWORDS_PROJECTION_MAP.put(Passwords.TIMES_USED, Passwords.TIMES_USED); michael@0: michael@0: URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "deleted-passwords", DELETED_PASSWORDS); michael@0: michael@0: DELETED_PASSWORDS_PROJECTION_MAP = new HashMap(); michael@0: DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.ID, DeletedPasswords.ID); michael@0: DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.GUID, DeletedPasswords.GUID); michael@0: DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.TIME_DELETED, DeletedPasswords.TIME_DELETED); michael@0: michael@0: // We don't use .loadMozGlue because we're in a different process, michael@0: // and we just want to reuse code rather than use the loader lock etc. michael@0: GeckoLoader.doLoadLibrary("mozglue"); michael@0: } michael@0: michael@0: public PasswordsProvider() { michael@0: super(LOG_TAG); michael@0: } michael@0: michael@0: @Override michael@0: protected String getDBName(){ michael@0: return DB_FILENAME; michael@0: } michael@0: michael@0: @Override michael@0: protected String getTelemetryPrefix() { michael@0: return TELEMETRY_TAG; michael@0: } michael@0: michael@0: @Override michael@0: protected int getDBVersion(){ michael@0: return DB_VERSION; michael@0: } michael@0: michael@0: @Override michael@0: public String getType(Uri uri) { michael@0: final int match = URI_MATCHER.match(uri); michael@0: michael@0: switch (match) { michael@0: case PASSWORDS: michael@0: return Passwords.CONTENT_TYPE; michael@0: michael@0: case DELETED_PASSWORDS: michael@0: return DeletedPasswords.CONTENT_TYPE; michael@0: michael@0: default: michael@0: throw new UnsupportedOperationException("Unknown type " + uri); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public String getTable(Uri uri) { michael@0: final int match = URI_MATCHER.match(uri); michael@0: switch (match) { michael@0: case DELETED_PASSWORDS: michael@0: return TABLE_DELETED_PASSWORDS; michael@0: michael@0: case PASSWORDS: michael@0: return TABLE_PASSWORDS; michael@0: michael@0: default: michael@0: throw new UnsupportedOperationException("Unknown table " + uri); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public String getSortOrder(Uri uri, String aRequested) { michael@0: if (!TextUtils.isEmpty(aRequested)) { michael@0: return aRequested; michael@0: } michael@0: michael@0: final int match = URI_MATCHER.match(uri); michael@0: switch (match) { michael@0: case DELETED_PASSWORDS: michael@0: return DEFAULT_DELETED_PASSWORDS_SORT_ORDER; michael@0: michael@0: case PASSWORDS: michael@0: return DEFAULT_PASSWORDS_SORT_ORDER; michael@0: michael@0: default: michael@0: throw new UnsupportedOperationException("Unknown URI " + uri); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void setupDefaults(Uri uri, ContentValues values) michael@0: throws IllegalArgumentException { michael@0: int match = URI_MATCHER.match(uri); michael@0: long now = System.currentTimeMillis(); michael@0: switch (match) { michael@0: case DELETED_PASSWORDS: michael@0: values.put(DeletedPasswords.TIME_DELETED, now); michael@0: michael@0: // Deleted passwords must contain a guid michael@0: if (!values.containsKey(Passwords.GUID)) { michael@0: throw new IllegalArgumentException("Must provide a GUID for a deleted password"); michael@0: } michael@0: break; michael@0: michael@0: case PASSWORDS: michael@0: values.put(Passwords.TIME_CREATED, now); michael@0: michael@0: // Generate GUID for new password. Don't override specified GUIDs. michael@0: if (!values.containsKey(Passwords.GUID)) { michael@0: String guid = Utils.generateGuid(); michael@0: values.put(Passwords.GUID, guid); michael@0: } michael@0: String nowString = new Long(now).toString(); michael@0: DBUtils.replaceKey(values, null, Passwords.HOSTNAME, ""); michael@0: DBUtils.replaceKey(values, null, Passwords.HTTP_REALM, ""); michael@0: DBUtils.replaceKey(values, null, Passwords.FORM_SUBMIT_URL, ""); michael@0: DBUtils.replaceKey(values, null, Passwords.USERNAME_FIELD, ""); michael@0: DBUtils.replaceKey(values, null, Passwords.PASSWORD_FIELD, ""); michael@0: DBUtils.replaceKey(values, null, Passwords.ENCRYPTED_USERNAME, ""); michael@0: DBUtils.replaceKey(values, null, Passwords.ENCRYPTED_PASSWORD, ""); michael@0: DBUtils.replaceKey(values, null, Passwords.ENC_TYPE, "0"); michael@0: DBUtils.replaceKey(values, null, Passwords.TIME_LAST_USED, nowString); michael@0: DBUtils.replaceKey(values, null, Passwords.TIME_PASSWORD_CHANGED, nowString); michael@0: DBUtils.replaceKey(values, null, Passwords.TIMES_USED, "0"); michael@0: break; michael@0: michael@0: default: michael@0: throw new UnsupportedOperationException("Unknown URI " + uri); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void initGecko() { michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Passwords:Init", null)); michael@0: Intent initIntent = new Intent(GeckoApp.ACTION_INIT_PW); michael@0: mContext.sendBroadcast(initIntent); michael@0: } michael@0: michael@0: private String doCrypto(String initialValue, Uri uri, Boolean encrypt) { michael@0: String profilePath = null; michael@0: if (uri != null) { michael@0: profilePath = uri.getQueryParameter(BrowserContract.PARAM_PROFILE_PATH); michael@0: } michael@0: michael@0: String result = ""; michael@0: try { michael@0: if (encrypt) { michael@0: if (profilePath != null) { michael@0: result = NSSBridge.encrypt(mContext, profilePath, initialValue); michael@0: } else { michael@0: result = NSSBridge.encrypt(mContext, initialValue); michael@0: } michael@0: } else { michael@0: if (profilePath != null) { michael@0: result = NSSBridge.decrypt(mContext, profilePath, initialValue); michael@0: } else { michael@0: result = NSSBridge.decrypt(mContext, initialValue); michael@0: } michael@0: } michael@0: } catch (Exception ex) { michael@0: Log.e(LOG_TAG, "Error in NSSBridge"); michael@0: throw new RuntimeException(ex); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: @Override michael@0: public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) { michael@0: if (values.containsKey(Passwords.GUID)) { michael@0: String guid = values.getAsString(Passwords.GUID); michael@0: if (guid == null) { michael@0: db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_NULL, null); michael@0: return; michael@0: } michael@0: String[] args = new String[] { guid }; michael@0: db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_VALUE, args); michael@0: } michael@0: michael@0: if (values.containsKey(Passwords.ENCRYPTED_PASSWORD)) { michael@0: String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_PASSWORD), uri, true); michael@0: values.put(Passwords.ENCRYPTED_PASSWORD, res); michael@0: values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR); michael@0: } michael@0: michael@0: if (values.containsKey(Passwords.ENCRYPTED_USERNAME)) { michael@0: String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_USERNAME), uri, true); michael@0: values.put(Passwords.ENCRYPTED_USERNAME, res); michael@0: values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db) { michael@0: if (values.containsKey(Passwords.ENCRYPTED_PASSWORD)) { michael@0: String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_PASSWORD), uri, true); michael@0: values.put(Passwords.ENCRYPTED_PASSWORD, res); michael@0: values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR); michael@0: } michael@0: michael@0: if (values.containsKey(Passwords.ENCRYPTED_USERNAME)) { michael@0: String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_USERNAME), uri, true); michael@0: values.put(Passwords.ENCRYPTED_USERNAME, res); michael@0: values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db) { michael@0: int passwordIndex = -1; michael@0: int usernameIndex = -1; michael@0: String profilePath = null; michael@0: michael@0: try { michael@0: passwordIndex = cursor.getColumnIndexOrThrow(Passwords.ENCRYPTED_PASSWORD); michael@0: } catch(Exception ex) { } michael@0: try { michael@0: usernameIndex = cursor.getColumnIndexOrThrow(Passwords.ENCRYPTED_USERNAME); michael@0: } catch(Exception ex) { } michael@0: michael@0: if (passwordIndex > -1 || usernameIndex > -1) { michael@0: MatrixBlobCursor m = (MatrixBlobCursor)cursor; michael@0: if (cursor.moveToFirst()) { michael@0: do { michael@0: if (passwordIndex > -1) { michael@0: String decrypted = doCrypto(cursor.getString(passwordIndex), uri, false);; michael@0: m.set(passwordIndex, decrypted); michael@0: } michael@0: michael@0: if (usernameIndex > -1) { michael@0: String decrypted = doCrypto(cursor.getString(usernameIndex), uri, false); michael@0: m.set(usernameIndex, decrypted); michael@0: } michael@0: } while(cursor.moveToNext()); michael@0: } michael@0: } michael@0: } michael@0: }