remove unified query from KeychainProvider

This commit is contained in:
Vincent Breitmoser
2018-06-27 23:51:25 +02:00
parent febe9cbe92
commit b819e1a83e
7 changed files with 11 additions and 718 deletions

View File

@@ -28,7 +28,6 @@ import com.tokenautocomplete.TokenCompleteTextView;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import static android.support.test.InstrumentationRegistry.getTargetContext; import static android.support.test.InstrumentationRegistry.getTargetContext;

View File

@@ -31,7 +31,6 @@ import com.nispok.snackbar.Snackbar;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;

View File

@@ -52,7 +52,6 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.BackupKeyringParcel; import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -75,12 +74,6 @@ import timber.log.Timber;
* either the name of a file or an output uri to write to. * either the name of a file or an output uri to write to.
*/ */
public class BackupOperation extends BaseOperation<BackupKeyringParcel> { public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
private static final String[] PROJECTION = new String[] {
KeyRings.MASTER_KEY_ID,
KeyRings.HAS_ANY_SECRET
};
// this is a very simple matcher, we only need basic sanitization // this is a very simple matcher, we only need basic sanitization
private static final Pattern HEADER_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+: [^\\n]+"); private static final Pattern HEADER_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+: [^\\n]+");

View File

@@ -97,34 +97,14 @@ public class KeychainContract {
public static final String BASE_KEY_SIGNATURES = "key_signatures"; public static final String BASE_KEY_SIGNATURES = "key_signatures";
public static final String PATH_UNIFIED = "unified";
public static final String PATH_PUBLIC = "public"; public static final String PATH_PUBLIC = "public";
public static final String PATH_USER_IDS = "user_ids"; public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys"; public static final String PATH_KEYS = "keys";
public static final String PATH_CERTS = "certs"; public static final String PATH_CERTS = "certs";
public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns { public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns {
public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID;
public static final String IS_REVOKED = KeysColumns.IS_REVOKED;
public static final String IS_SECURE = KeysColumns.IS_SECURE;
public static final String VERIFIED = CertsColumns.VERIFIED;
public static final String IS_EXPIRED = "is_expired";
public static final String HAS_ANY_SECRET = "has_any_secret";
public static final String HAS_ENCRYPT = "has_encrypt";
public static final String HAS_SIGN_SECRET = "has_sign_secret";
public static final String HAS_CERTIFY_SECRET = "has_certify_secret";
public static final String HAS_AUTHENTICATE = "has_authenticate";
public static final String HAS_AUTHENTICATE_SECRET = "has_authenticate_secret";
public static final String HAS_DUPLICATE_USER_ID = "has_duplicate_user_id";
public static final String API_KNOWN_TO_PACKAGE_NAMES = "known_to_apps";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build(); .appendPath(BASE_KEY_RINGS).build();
public static Uri buildUnifiedKeyRingsUri() {
return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build();
}
} }
public static class KeyRingData implements KeyRingsColumns, BaseColumns { public static class KeyRingData implements KeyRingsColumns, BaseColumns {

View File

@@ -18,11 +18,6 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import android.arch.persistence.db.SupportSQLiteDatabase; import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentValues; import android.content.ContentValues;
@@ -30,32 +25,21 @@ import android.content.UriMatcher;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.model.AutocryptPeer;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.util.DatabaseUtil;
import timber.log.Timber; import timber.log.Timber;
import static android.database.DatabaseUtils.dumpCursorToString;
public class KeychainProvider extends ContentProvider implements SimpleContentResolverInterface { public class KeychainProvider extends ContentProvider implements SimpleContentResolverInterface {
private static final int KEY_RINGS_UNIFIED = 101;
private static final int KEY_RING_UNIFIED = 200;
private static final int KEY_RING_KEYS = 201; private static final int KEY_RING_KEYS = 201;
private static final int KEY_RING_USER_IDS = 202; private static final int KEY_RING_USER_IDS = 202;
private static final int KEY_RING_PUBLIC = 203; private static final int KEY_RING_PUBLIC = 203;
@@ -74,33 +58,16 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
String authority = KeychainContract.CONTENT_AUTHORITY; String authority = KeychainContract.CONTENT_AUTHORITY;
/*
* list key_rings
*
* <pre>
* key_rings/unified
* key_rings/user_ids
* </pre>
*/
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS
+ "/" + KeychainContract.PATH_UNIFIED,
KEY_RINGS_UNIFIED);
/* /*
* list key_ring specifics * list key_ring specifics
* *
* <pre> * <pre>
* key_rings/_/unified
* key_rings/_/keys * key_rings/_/keys
* key_rings/_/user_ids * key_rings/_/user_ids
* key_rings/_/linked_ids
* key_rings/_/public * key_rings/_/public
* key_rings/_/certs * key_rings/_/certs
* </pre> * </pre>
*/ */
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ KeychainContract.PATH_UNIFIED,
KEY_RING_UNIFIED);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
+ KeychainContract.PATH_KEYS, + KeychainContract.PATH_KEYS,
KEY_RING_KEYS); KEY_RING_KEYS);
@@ -122,9 +89,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
private KeychainDatabase mKeychainDatabase; private KeychainDatabase mKeychainDatabase;
/**
* {@inheritDoc}
*/
@Override @Override
public boolean onCreate() { public boolean onCreate() {
mUriMatcher = buildUriMatcher(); mUriMatcher = buildUriMatcher();
@@ -132,234 +96,28 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
} }
public KeychainDatabase getDb() { public KeychainDatabase getDb() {
if(mKeychainDatabase == null) if(mKeychainDatabase == null) {
mKeychainDatabase = KeychainDatabase.getInstance(getContext()); mKeychainDatabase = KeychainDatabase.getInstance(getContext());
}
return mKeychainDatabase; return mKeychainDatabase;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public String getType(@NonNull Uri uri) { public String getType(@NonNull Uri uri) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public Cursor query(
@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
throw new UnsupportedOperationException();
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, public Uri insert(@NonNull Uri uri, ContentValues values) {
String sortOrder) {
Timber.v("query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
int match = mUriMatcher.match(uri);
// all query() parameters, for good measure
String groupBy;
switch (match) {
case KEY_RING_UNIFIED:
case KEY_RINGS_UNIFIED: {
HashMap<String, String> projectionMap = new HashMap<>();
projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id");
projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
projectionMap.put(KeyRings.KEY_ID, Tables.KEYS + "." + Keys.KEY_ID);
projectionMap.put(KeyRings.KEY_SIZE, Tables.KEYS + "." + Keys.KEY_SIZE);
projectionMap.put(KeyRings.KEY_CURVE_OID, Tables.KEYS + "." + Keys.KEY_CURVE_OID);
projectionMap.put(KeyRings.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED);
projectionMap.put(KeyRings.IS_SECURE, Tables.KEYS + "." + Keys.IS_SECURE);
projectionMap.put(KeyRings.CAN_CERTIFY, Tables.KEYS + "." + Keys.CAN_CERTIFY);
projectionMap.put(KeyRings.CAN_ENCRYPT, Tables.KEYS + "." + Keys.CAN_ENCRYPT);
projectionMap.put(KeyRings.CAN_SIGN, Tables.KEYS + "." + Keys.CAN_SIGN);
projectionMap.put(KeyRings.CAN_AUTHENTICATE, Tables.KEYS + "." + Keys.CAN_AUTHENTICATE);
projectionMap.put(KeyRings.CREATION, Tables.KEYS + "." + Keys.CREATION);
projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY);
projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM);
projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT);
projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
projectionMap.put(KeyRings.NAME, Tables.USER_PACKETS + "." + UserPackets.NAME);
projectionMap.put(KeyRings.EMAIL, Tables.USER_PACKETS + "." + UserPackets.EMAIL);
projectionMap.put(KeyRings.COMMENT, Tables.USER_PACKETS + "." + UserPackets.COMMENT);
projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID,
"(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups"
+ " WHERE dups." + UserPackets.MASTER_KEY_ID
+ " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND dups." + UserPackets.RANK + " = 0"
+ " AND dups." + UserPackets.NAME
+ " = " + Tables.USER_PACKETS + "." + UserPackets.NAME + " COLLATE NOCASE"
+ " AND dups." + UserPackets.EMAIL
+ " = " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " COLLATE NOCASE"
+ ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID);
projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
projectionMap.put(KeyRings.HAS_SECRET, Tables.KEYS + "." + KeyRings.HAS_SECRET);
projectionMap.put(KeyRings.HAS_ANY_SECRET,
"(EXISTS (SELECT * FROM " + Tables.KEYS + " AS k WHERE "
+ "k." + Keys.HAS_SECRET + " != 0"
+ " AND k." + Keys.MASTER_KEY_ID + " = "
+ Tables.KEYS + "." + KeyRingData.MASTER_KEY_ID
+ ")) AS " + KeyRings.HAS_ANY_SECRET);
projectionMap.put(KeyRings.HAS_ENCRYPT,
"kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT);
projectionMap.put(KeyRings.HAS_SIGN_SECRET,
"kS." + Keys.KEY_ID + " AS " + KeyRings.HAS_SIGN_SECRET);
projectionMap.put(KeyRings.HAS_AUTHENTICATE,
"kA." + Keys.KEY_ID + " AS " + KeyRings.HAS_AUTHENTICATE);
projectionMap.put(KeyRings.HAS_AUTHENTICATE_SECRET,
"kA." + Keys.KEY_ID + " AS " + KeyRings.HAS_AUTHENTICATE_SECRET);
projectionMap.put(KeyRings.HAS_CERTIFY_SECRET,
"kC." + Keys.KEY_ID + " AS " + KeyRings.HAS_CERTIFY_SECRET);
projectionMap.put(KeyRings.IS_EXPIRED,
"(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY
+ " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED);
projectionMap.put(KeyRings.API_KNOWN_TO_PACKAGE_NAMES,
"GROUP_CONCAT(DISTINCT aTI." + AutocryptPeer.PACKAGE_NAME + ") AS "
+ KeyRings.API_KNOWN_TO_PACKAGE_NAMES);
qb.setProjectionMap(projectionMap);
if (projection == null) {
throw new IllegalArgumentException("Please provide a projection!");
}
// Need this as list so we can search in it
List<String> plist = Arrays.asList(projection);
qb.setTables(
Tables.KEYS
+ " INNER JOIN " + Tables.USER_PACKETS + " ON ("
+ Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " = "
+ Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
// we KNOW that the rank zero user packet is a user id!
+ " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = 0"
+ ") LEFT JOIN " + Tables.CERTS + " ON ("
+ Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " = "
+ Tables.CERTS + "." + KeyRings.MASTER_KEY_ID
+ " AND " + Tables.CERTS + "." + Certs.VERIFIED
+ " = " + Certs.VERIFIED_SECRET
+ ")"
// fairly expensive joins following, only do when requested
+ (plist.contains(KeyRings.HAS_ENCRYPT) ?
" LEFT JOIN " + Tables.KEYS + " AS kE ON ("
+"kE." + Keys.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND kE." + Keys.IS_REVOKED + " = 0"
+ " AND kE." + Keys.IS_SECURE + " = 1"
+ " AND kE." + Keys.CAN_ENCRYPT + " = 1"
+ " AND ( kE." + Keys.EXPIRY + " IS NULL OR kE." + Keys.EXPIRY
+ " >= " + new Date().getTime() / 1000 + " )"
+ ")" : "")
+ (plist.contains(KeyRings.HAS_SIGN_SECRET) ?
" LEFT JOIN " + Tables.KEYS + " AS kS ON ("
+"kS." + Keys.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND kS." + Keys.IS_REVOKED + " = 0"
+ " AND kS." + Keys.IS_SECURE + " = 1"
+ " AND kS." + Keys.CAN_SIGN + " = 1"
+ " AND kS." + Keys.HAS_SECRET + " > 1"
+ " AND ( kS." + Keys.EXPIRY + " IS NULL OR kS." + Keys.EXPIRY
+ " >= " + new Date().getTime() / 1000 + " )"
+ ")" : "")
+ (plist.contains(KeyRings.HAS_AUTHENTICATE) ?
" LEFT JOIN " + Tables.KEYS + " AS kA ON ("
+"kA." + Keys.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND kA." + Keys.IS_REVOKED + " = 0"
+ " AND kA." + Keys.IS_SECURE + " = 1"
+ " AND kA." + Keys.CAN_AUTHENTICATE + " = 1"
+ " AND ( kA." + Keys.EXPIRY + " IS NULL OR kA." + Keys.EXPIRY
+ " >= " + new Date().getTime() / 1000 + " )"
+ ")" : "")
+ (plist.contains(KeyRings.HAS_AUTHENTICATE_SECRET) ?
" LEFT JOIN " + Tables.KEYS + " AS kA ON ("
+"kA." + Keys.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND kA." + Keys.IS_REVOKED + " = 0"
+ " AND kA." + Keys.IS_SECURE + " = 1"
+ " AND kA." + Keys.CAN_AUTHENTICATE + " = 1"
+ " AND kA." + Keys.HAS_SECRET + " > 1"
+ " AND ( kA." + Keys.EXPIRY + " IS NULL OR kA." + Keys.EXPIRY
+ " >= " + new Date().getTime() / 1000 + " )"
+ ")" : "")
+ (plist.contains(KeyRings.HAS_CERTIFY_SECRET) ?
" LEFT JOIN " + Tables.KEYS + " AS kC ON ("
+"kC." + Keys.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND kC." + Keys.IS_REVOKED + " = 0"
+ " AND kC." + Keys.IS_SECURE + " = 1"
+ " AND kC." + Keys.CAN_CERTIFY + " = 1"
+ " AND kC." + Keys.HAS_SECRET + " > 1"
+ " AND ( kC." + Keys.EXPIRY + " IS NULL OR kC." + Keys.EXPIRY
+ " >= " + new Date().getTime() / 1000 + " )"
+ ")" : "")
+ (plist.contains(KeyRings.API_KNOWN_TO_PACKAGE_NAMES) ?
" LEFT JOIN " + AutocryptPeer.TABLE_NAME + " AS aTI ON ("
+"aTI." + AutocryptPeer.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ ")" : "")
);
qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0");
// in case there are multiple verifying certificates
groupBy = Tables.KEYS + "." + Keys.MASTER_KEY_ID;
qb.appendWhere(" AND " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
if (TextUtils.isEmpty(sortOrder)) {
sortOrder = Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC";
}
// uri to watch is all /key_rings/
uri = KeyRings.CONTENT_URI;
break;
}
default: {
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
}
}
// If no sort order is specified use the default
String orderBy;
if (TextUtils.isEmpty(sortOrder)) {
orderBy = null;
} else {
orderBy = sortOrder;
}
SupportSQLiteDatabase db = getDb().getReadableDatabase();
String query = qb.buildQuery(projection, selection, groupBy, null, orderBy, null);
Cursor cursor = db.query(query, selectionArgs);
if (cursor != null) {
// Tell the cursor what uri to watch, so it knows when its source data changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
Timber.d("Query: " + qb.buildQuery(projection, selection, null, null, orderBy, null));
if (Constants.DEBUG && Constants.DEBUG_LOG_DB_QUERIES) {
Timber.d("Cursor: " + dumpCursorToString(cursor));
}
if (Constants.DEBUG && Constants.DEBUG_EXPLAIN_QUERIES) {
String rawQuery = qb.buildQuery(projection, selection, groupBy, null, orderBy, null);
DatabaseUtil.explainQuery(db, rawQuery);
}
return cursor;
}
/**
* {@inheritDoc}
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")"); Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")");
final SupportSQLiteDatabase db = getDb().getWritableDatabase(); final SupportSQLiteDatabase db = getDb().getWritableDatabase();
@@ -372,7 +130,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
switch (match) { switch (match) {
case KEY_RING_PUBLIC: { case KEY_RING_PUBLIC: {
db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values); db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); keyId = values.getAsLong(Keys.MASTER_KEY_ID);
break; break;
} }
case KEY_RING_KEYS: { case KEY_RING_KEYS: {
@@ -424,19 +182,13 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
return rowUri; return rowUri;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) { public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* {@inheritDoc}
*/
@Override @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")"); Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")");
final SupportSQLiteDatabase db = getDb().getWritableDatabase(); final SupportSQLiteDatabase db = getDb().getWritableDatabase();

View File

@@ -1,332 +0,0 @@
/*
* Copyright (C) 2017 Schürmann & Breitmoser GbR
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.adapter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
public class KeyAdapter extends CursorAdapter {
protected String mQuery;
protected LayoutInflater mInflater;
protected Context mContext;
// These are the rows that we will retrieve.
public static final String[] PROJECTION = new String[]{
KeyRings._ID,
KeyRings.MASTER_KEY_ID,
KeyRings.USER_ID,
KeyRings.IS_REVOKED,
KeyRings.IS_EXPIRED,
KeyRings.IS_SECURE,
KeyRings.VERIFIED,
KeyRings.HAS_ANY_SECRET,
KeyRings.HAS_DUPLICATE_USER_ID,
KeyRings.FINGERPRINT,
KeyRings.CREATION,
KeyRings.HAS_ENCRYPT,
KeyRings.NAME,
KeyRings.EMAIL,
KeyRings.COMMENT
};
public static final int INDEX_MASTER_KEY_ID = 1;
public static final int INDEX_USER_ID = 2;
public static final int INDEX_IS_REVOKED = 3;
public static final int INDEX_IS_EXPIRED = 4;
public static final int INDEX_IS_SECURE = 5;
public static final int INDEX_VERIFIED = 6;
public static final int INDEX_HAS_ANY_SECRET = 7;
public static final int INDEX_HAS_DUPLICATE_USER_ID = 8;
public static final int INDEX_FINGERPRINT = 9;
public static final int INDEX_CREATION = 10;
public static final int INDEX_HAS_ENCRYPT = 11;
public static final int INDEX_NAME = 12;
public static final int INDEX_EMAIL = 13;
public static final int INDEX_COMMENT = 14;
public KeyAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mContext = context;
mInflater = LayoutInflater.from(context);
}
public void setSearchQuery(String query) {
mQuery = query;
}
public static class KeyItemViewHolder {
public View mView;
public View mLayoutData;
public Long mMasterKeyId;
public TextView mMainUserId;
public TextView mMainUserIdRest;
public TextView mCreationDate;
public ImageView mStatus;
public KeyItem mDisplayedItem;
public KeyItemViewHolder(View view) {
mView = view;
mLayoutData = view.findViewById(R.id.key_list_item_data);
mMainUserId = view.findViewById(R.id.key_list_item_name);
mMainUserIdRest = view.findViewById(R.id.key_list_item_email);
mStatus = view.findViewById(R.id.key_list_item_status_icon);
mCreationDate = view.findViewById(R.id.key_list_item_creation);
}
public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) {
mDisplayedItem = item;
{ // set name and stuff, common to both key types
OpenPgpUtils.UserId userIdSplit = item.mUserId;
if (userIdSplit.name != null) {
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
} else {
mMainUserId.setText(R.string.user_id_no_name);
}
if (userIdSplit.email != null) {
mMainUserIdRest.setText(highlighter.highlight(userIdSplit.email));
mMainUserIdRest.setVisibility(View.VISIBLE);
} else {
mMainUserIdRest.setVisibility(View.GONE);
}
}
// sort of a hack: if this item isn't enabled, we make it clickable
// to intercept its click events. either way, no listener!
mView.setClickable(!enabled);
{ // set edit button and status, specific by key type
mMasterKeyId = item.mKeyId;
int textColor;
// Note: order is important!
if (item.mIsRevoked) {
KeyFormattingUtils
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
textColor = context.getResources().getColor(R.color.key_flag_gray);
} else if (item.mIsExpired) {
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
textColor = context.getResources().getColor(R.color.key_flag_gray);
} else if (!item.mIsSecure) {
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.INSECURE, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
textColor = context.getResources().getColor(R.color.key_flag_gray);
} else if (item.mIsSecret) {
mStatus.setVisibility(View.GONE);
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
} else {
// this is a public key - show if it's verified
if (item.mIsVerified) {
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
mStatus.setVisibility(View.VISIBLE);
} else {
KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED);
mStatus.setVisibility(View.VISIBLE);
}
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
}
if (!enabled) {
textColor = context.getResources().getColor(R.color.key_flag_gray);
}
mMainUserId.setTextColor(textColor);
mMainUserIdRest.setTextColor(textColor);
if (item.mHasDuplicate) {
String dateTime = DateUtils.formatDateTime(context,
item.mCreation.getTime(),
DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH);
mCreationDate.setText(context.getString(R.string.label_key_created,
dateTime));
mCreationDate.setTextColor(textColor);
mCreationDate.setVisibility(View.VISIBLE);
} else {
mCreationDate.setVisibility(View.GONE);
}
}
}
}
public boolean isEnabled(Cursor cursor) {
return true;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
KeyItemViewHolder holder = new KeyItemViewHolder(view);
view.setTag(holder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
Highlighter highlighter = new Highlighter(context, mQuery);
KeyItem item = new KeyItem(cursor);
boolean isEnabled = isEnabled(cursor);
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
h.setData(context, item, highlighter, isEnabled);
}
public boolean isSecretAvailable(int id) {
if (!mCursor.moveToPosition(id)) {
throw new IllegalStateException("couldn't move cursor to position " + id);
}
return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
}
public long getMasterKeyId(int id) {
if (!mCursor.moveToPosition(id)) {
throw new IllegalStateException("couldn't move cursor to position " + id);
}
return mCursor.getLong(INDEX_MASTER_KEY_ID);
}
@Override
public KeyItem getItem(int position) {
Cursor c = getCursor();
if (c.isClosed() || !c.moveToPosition(position)) {
return null;
}
return new KeyItem(c);
}
@Override
public long getItemId(int position) {
Cursor cursor = getCursor();
// prevent a crash on rapid cursor changes
if (cursor != null && getCursor().isClosed()) {
return 0L;
}
return super.getItemId(position);
}
// must be serializable for TokenCompleTextView state
public static class KeyItem implements Serializable {
public final String mUserIdFull;
public final OpenPgpUtils.UserId mUserId;
public final String mName;
public final String mEmail;
public final String mComment;
public final long mKeyId;
public final boolean mHasDuplicate;
public final boolean mHasEncrypt;
public final Date mCreation;
public final String mFingerprint;
public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsSecure, mIsVerified;
private KeyItem(Cursor cursor) {
String userId = cursor.getString(INDEX_USER_ID);
mUserId = KeyRing.splitUserId(userId);
mName = cursor.getString(INDEX_NAME);
mEmail = cursor.getString(INDEX_EMAIL);
mComment = cursor.getString(INDEX_COMMENT);
mUserIdFull = userId;
mKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
mHasDuplicate = cursor.getLong(INDEX_HAS_DUPLICATE_USER_ID) > 0;
mHasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0;
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
cursor.getBlob(INDEX_FINGERPRINT));
mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0;
mIsSecure = cursor.getInt(INDEX_IS_SECURE) > 0;
mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0;
}
public KeyItem(CanonicalizedPublicKeyRing ring) {
CanonicalizedPublicKey key = ring.getPublicKey();
String userId = key.getPrimaryUserIdWithFallback();
mUserId = KeyRing.splitUserId(userId);
mName = mUserId.name;
mEmail = mUserId.email;
mComment = mUserId.comment;
mUserIdFull = userId;
mKeyId = ring.getMasterKeyId();
mHasDuplicate = false;
mHasEncrypt = key.getKeyRing().getEncryptIds().size() > 0;
mCreation = key.getCreationTime();
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
ring.getFingerprint());
mIsRevoked = key.isRevoked();
mIsExpired = key.isExpired();
mIsSecure = key.isSecure();
// these two are actually "don't know"s
mIsSecret = false;
mIsVerified = false;
}
public String getReadableName() {
if (mName != null) {
return mName;
} else {
return mEmail;
}
}
}
public static String[] getProjectionWith(String[] projection) {
List<String> list = new ArrayList<>();
list.addAll(Arrays.asList(PROJECTION));
list.addAll(Arrays.asList(projection));
return list.toArray(new String[list.size()]);
}
}

View File

@@ -1,98 +0,0 @@
/*
* Copyright (C) 2017 Schürmann & Breitmoser GbR
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CheckBox;
import org.sufficientlysecure.keychain.R;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class KeySelectableAdapter extends KeyAdapter implements OnItemClickListener {
private HashSet<Long> mSelectedItems = new HashSet<>();
public KeySelectableAdapter(Context context, Cursor c, int flags, Set<Long> initialChecked) {
super(context, c, flags);
if (initialChecked != null) {
mSelectedItems.addAll(initialChecked);
}
}
public static class KeySelectableItemViewHolder extends KeyItemViewHolder {
public CheckBox mCheckbox;
public KeySelectableItemViewHolder(View view) {
super(view);
mCheckbox = view.findViewById(R.id.selected);
}
public void setCheckedState(boolean checked) {
mCheckbox.setChecked(checked);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.key_list_selectable_item, parent, false);
KeySelectableItemViewHolder holder = new KeySelectableItemViewHolder(view);
view.setTag(holder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor);
KeySelectableItemViewHolder h = (KeySelectableItemViewHolder) view.getTag();
h.setCheckedState(mSelectedItems.contains(h.mDisplayedItem.mKeyId));
}
public void setCheckedStates(Set<Long> checked) {
mSelectedItems.clear();
mSelectedItems.addAll(checked);
notifyDataSetChanged();
}
public Set<Long> getSelectedMasterKeyIds() {
return Collections.unmodifiableSet(mSelectedItems);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
long masterKeyId = getMasterKeyId(position);
if (mSelectedItems.contains(masterKeyId)) {
mSelectedItems.remove(masterKeyId);
} else {
mSelectedItems.add(masterKeyId);
}
notifyDataSetChanged();
}
}