use SQLDelight, remove ApiApps access from KeychainProvider

This commit is contained in:
Vincent Breitmoser 2018-06-15 19:46:55 +02:00
parent 59c9f52e85
commit d133b732e5
30 changed files with 628 additions and 962 deletions

View file

@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
apply plugin: 'jacoco'
apply plugin: 'com.squareup.sqldelight'
// apply plugin: 'com.github.kt3k.coveralls'
dependencies {
@ -98,6 +99,8 @@ dependencies {
compile "android.arch.lifecycle:extensions:1.0.0"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
compile "android.arch.persistence:db-framework:1.0.0"
}
// Output of ./gradlew -q calculateChecksums

View file

@ -0,0 +1,11 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.ApiAllowedKeysModel;
@AutoValue
public abstract class ApiAllowedKey implements ApiAllowedKeysModel {
public static final Factory<ApiAllowedKey> FACTORY = new Factory<ApiAllowedKey>(AutoValue_ApiAllowedKey::new);
}

View file

@ -0,0 +1,33 @@
/*
* 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.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.ApiAppsModel;
@AutoValue
public abstract class ApiApp implements ApiAppsModel {
public static final ApiAppsModel.Factory<ApiApp> FACTORY =
new ApiAppsModel.Factory<ApiApp>(AutoValue_ApiApp::new);
public static ApiApp create(String packageName, byte[] packageSignature) {
return new AutoValue_ApiApp(null, packageName, packageSignature);
}
}

View file

@ -20,179 +20,97 @@ package org.sufficientlysecure.keychain.provider;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.remote.AppSettings;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.ApiAllowedKeysModel.InsertAllowedKey;
import org.sufficientlysecure.keychain.ApiAppsModel;
import org.sufficientlysecure.keychain.ApiAppsModel.DeleteByPackageName;
import org.sufficientlysecure.keychain.ApiAppsModel.InsertApiApp;
import org.sufficientlysecure.keychain.model.ApiAllowedKey;
import org.sufficientlysecure.keychain.model.ApiApp;
public class ApiDataAccessObject {
private final SimpleContentResolverInterface mQueryInterface;
private final SupportSQLiteDatabase db;
public ApiDataAccessObject(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
mQueryInterface = new SimpleContentResolverInterface() {
@Override
public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
}
@Override
public Uri insert(Uri contentUri, ContentValues values) {
return contentResolver.insert(contentUri, values);
}
@Override
public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) {
return contentResolver.update(contentUri, values, where, selectionArgs);
}
@Override
public int delete(Uri contentUri, String where, String[] selectionArgs) {
return contentResolver.delete(contentUri, where, selectionArgs);
}
};
KeychainDatabase keychainDatabase = new KeychainDatabase(context);
db = keychainDatabase.getWritableDatabase();
}
public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) {
mQueryInterface = queryInterface;
}
public ArrayList<String> getRegisteredApiApps() {
Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null);
ArrayList<String> packageNames = new ArrayList<>();
try {
if (cursor != null) {
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
if (cursor.moveToFirst()) {
do {
packageNames.add(cursor.getString(packageNameCol));
} while (cursor.moveToNext());
}
}
} finally {
if (cursor != null) {
cursor.close();
public ApiApp getApiApp(String packageName) {
try (Cursor cursor = db.query(ApiApp.FACTORY.selectByPackageName(packageName))) {
if (cursor.moveToFirst()) {
return ApiApp.FACTORY.selectByPackageNameMapper().map(cursor);
}
return null;
}
return packageNames;
}
private ContentValues contentValueForApiApps(AppSettings appSettings) {
ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate());
return values;
}
public void insertApiApp(AppSettings appSettings) {
mQueryInterface.insert(ApiApps.CONTENT_URI, contentValueForApiApps(appSettings));
}
public void deleteApiApp(String packageName) {
mQueryInterface.delete(ApiApps.buildByPackageNameUri(packageName), null, null);
}
/**
* Must be an uri pointing to an account
*/
public AppSettings getApiAppSettings(Uri uri) {
AppSettings settings = null;
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
settings = new AppSettings();
settings.setPackageName(cursor.getString(
cursor.getColumnIndex(ApiApps.PACKAGE_NAME)));
settings.setPackageCertificate(cursor.getBlob(
cursor.getColumnIndex(ApiApps.PACKAGE_CERTIFICATE)));
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return settings;
}
public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
HashSet<Long> keyIds = new HashSet<>();
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
try {
if (cursor != null) {
int keyIdColumn = cursor.getColumnIndex(ApiAllowedKeys.KEY_ID);
while (cursor.moveToNext()) {
keyIds.add(cursor.getLong(keyIdColumn));
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return keyIds;
}
public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds)
throws RemoteException, OperationApplicationException {
// wipe whole table of allowed keys for this account
mQueryInterface.delete(uri, null, null);
// re-insert allowed key ids
for (Long keyId : allowedKeyIds) {
ContentValues values = new ContentValues();
values.put(ApiAllowedKeys.KEY_ID, keyId);
mQueryInterface.insert(uri, values);
}
}
public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) {
ContentValues values = new ContentValues();
values.put(ApiAllowedKeys.KEY_ID, allowedKeyId);
mQueryInterface.insert(uri, values);
}
public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) {
Uri uri = ApiAllowedKeys.buildBaseUri(packageName);
addAllowedKeyIdForApp(uri, allowedKeyId);
}
public byte[] getApiAppCertificate(String packageName) {
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName));
return ApiApp.FACTORY.getCertificateMapper().map(cursor);
}
String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE};
public void insertApiApp(ApiApp apiApp) {
InsertApiApp statement = new ApiAppsModel.InsertApiApp(db);
statement.bind(apiApp.package_name(), apiApp.package_signature());
statement.execute();
}
Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null);
try {
byte[] signature = null;
if (cursor != null && cursor.moveToFirst()) {
int signatureCol = 0;
public void deleteApiApp(String packageName) {
DeleteByPackageName deleteByPackageName = new DeleteByPackageName(db);
deleteByPackageName.bind(packageName);
deleteByPackageName.executeUpdateDelete();
}
signature = cursor.getBlob(signatureCol);
}
return signature;
} finally {
if (cursor != null) {
cursor.close();
public HashSet<Long> getAllowedKeyIdsForApp(String packageName) {
SqlDelightQuery allowedKeys = ApiAllowedKey.FACTORY.getAllowedKeys(packageName);
HashSet<Long> keyIds = new HashSet<>();
try (Cursor cursor = db.query(allowedKeys)) {
while (cursor.moveToNext()) {
long allowedKeyId = ApiAllowedKey.FACTORY.getAllowedKeysMapper().map(cursor);
keyIds.add(allowedKeyId);
}
}
return keyIds;
}
public void saveAllowedKeyIdsForApp(String packageName, Set<Long> allowedKeyIds) {
ApiAllowedKey.DeleteByPackageName deleteByPackageName = new ApiAllowedKey.DeleteByPackageName(db);
deleteByPackageName.bind(packageName);
deleteByPackageName.executeUpdateDelete();
InsertAllowedKey statement = new InsertAllowedKey(db);
for (Long keyId : allowedKeyIds) {
statement.bind(packageName, keyId);
statement.execute();
}
}
public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) {
InsertAllowedKey statement = new InsertAllowedKey(db);
statement.bind(packageName, allowedKeyId);
statement.execute();
}
public List<ApiApp> getAllApiApps() {
SqlDelightQuery query = ApiApp.FACTORY.selectAll();
ArrayList<ApiApp> result = new ArrayList<>();
try (Cursor cursor = db.query(query)) {
while (cursor.moveToNext()) {
ApiApp apiApp = ApiApp.FACTORY.selectAllMapper().map(cursor);
result.add(apiApp);
}
}
return result;
}
}

View file

@ -333,7 +333,7 @@ public class KeyRepository {
try {
return localSecretKeyStorage.readSecretKey(masterKeyId);
} catch (IOException e) {
Timber.e(e, "Error reading public key from storage!");
Timber.e(e, "Error reading secret key from storage!");
throw new NotFoundException();
}
}

View file

@ -141,9 +141,6 @@ public class KeychainContract {
public static final String PATH_KEYS = "keys";
public static final String PATH_CERTS = "certs";
public static final String BASE_API_APPS = "api_apps";
public static final String PATH_ALLOWED_KEYS = "allowed_keys";
public static final String PATH_BY_PACKAGE_NAME = "by_package_name";
public static final String PATH_BY_KEY_ID = "by_key_id";
@ -323,43 +320,6 @@ public class KeychainContract {
}
public static class ApiApps implements ApiAppsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps";
/**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.api_apps";
public static Uri buildByPackageNameUri(String packageName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build();
}
}
public static class ApiAllowedKeys implements ApiAppsAllowedKeysColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps.allowed_keys";
public static Uri buildBaseUri(String packageName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ALLOWED_KEYS)
.build();
}
}
public static class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_AUTOCRYPT_PEERS).build();

View file

@ -23,16 +23,18 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.db.SupportSQLiteOpenHelper;
import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;
import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeerColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
@ -53,10 +55,11 @@ import timber.log.Timber;
* - TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE).
* - BLOB. The value is a blob of data, stored exactly as it was input.
*/
public class KeychainDatabase extends SQLiteOpenHelper {
public class KeychainDatabase {
private static final String DATABASE_NAME = "openkeychain.db";
private static final int DATABASE_VERSION = 26;
private Context mContext;
private final SupportSQLiteOpenHelper supportSQLiteOpenHelper;
private Context context;
public interface Tables {
String KEY_RINGS_PUBLIC = "keyrings_public";
@ -65,18 +68,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_SIGNATURES = "key_signatures";
String USER_PACKETS = "user_packets";
String CERTS = "certs";
String API_APPS = "api_apps";
String API_ALLOWED_KEYS = "api_allowed_keys";
String OVERRIDDEN_WARNINGS = "overridden_warnings";
String API_AUTOCRYPT_PEERS = "api_autocrypt_peers";
}
private static final String CREATE_KEYRINGS_PUBLIC =
"CREATE TABLE IF NOT EXISTS keyrings_public ("
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
+ KeyRingsColumns.KEY_RING_DATA + " BLOB"
+ ")";
private static final String CREATE_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
+ KeysColumns.MASTER_KEY_ID + " INTEGER, "
@ -143,15 +139,6 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_UPDATE_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.UPDATED_KEYS + " ("
+ UpdatedKeysColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY, "
+ UpdatedKeysColumns.LAST_UPDATED + " INTEGER, "
+ UpdatedKeysColumns.SEEN_ON_KEYSERVERS + " INTEGER, "
+ "FOREIGN KEY(" + UpdatedKeysColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_KEY_SIGNATURES =
"CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " ("
+ KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, "
@ -175,16 +162,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", "
+ ApiAutocryptPeerColumns.IDENTIFIER + "), "
+ "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES "
+ Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ "api_apps (package_signature) ON DELETE CASCADE"
+ ")";
private static final String CREATE_API_APPS =
"CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
+ ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB"
+ ")";
private static final String CREATE_API_APPS_ALLOWED_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
@ -194,7 +174,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", "
+ ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), "
+ "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES "
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ "api_apps (" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_OVERRIDDEN_WARNINGS =
@ -204,12 +184,46 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ ")";
public KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
this.context = context;
supportSQLiteOpenHelper =
new FrameworkSQLiteOpenHelperFactory()
.create(Configuration.builder(context).name(DATABASE_NAME).callback(
new Callback(DATABASE_VERSION) {
@Override
public void onCreate(SupportSQLiteDatabase db) {
KeychainDatabase.this.onCreate(db);
}
@Override
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
KeychainDatabase.this.onUpgrade(db, oldVersion, newVersion);
}
@Override
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
KeychainDatabase.this.onDowngrade(db, oldVersion, newVersion);
}
@Override
public void onOpen(SupportSQLiteDatabase db) {
super.onOpen(db);
if (!db.isReadOnly()) {
// Enable foreign key constraints
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
}).build());
}
@Override
public void onCreate(SQLiteDatabase db) {
public SupportSQLiteDatabase getReadableDatabase() {
return supportSQLiteOpenHelper.getReadableDatabase();
}
public SupportSQLiteDatabase getWritableDatabase() {
return supportSQLiteOpenHelper.getWritableDatabase();
}
private void onCreate(SupportSQLiteDatabase db) {
Timber.w("Creating database...");
db.execSQL(CREATE_KEYRINGS_PUBLIC);
@ -218,7 +232,6 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_CERTS);
db.execSQL(CREATE_UPDATE_KEYS);
db.execSQL(CREATE_KEY_SIGNATURES);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
db.execSQL(CREATE_OVERRIDDEN_WARNINGS);
db.execSQL(CREATE_API_AUTOCRYPT_PEERS);
@ -231,20 +244,10 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("CREATE INDEX uids_by_email ON user_packets ("
+ UserPacketsColumns.EMAIL + ");");
Preferences.getPreferences(mContext).setKeySignaturesTableInitialized();
Preferences.getPreferences(context).setKeySignaturesTableInitialized();
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
if (!db.isReadOnly()) {
// Enable foreign key constraints
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
private void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
Timber.d("Upgrading db from " + oldVersion + " to " + newVersion);
switch (oldVersion) {
@ -459,9 +462,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
}
}
private void migrateSecretKeysFromDbToLocalStorage(SQLiteDatabase db) throws IOException {
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(mContext);
Cursor cursor = db.rawQuery("SELECT master_key_id, key_ring_data FROM keyrings_secret", null);
private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db) throws IOException {
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret");
while (cursor.moveToNext()) {
long masterKeyId = cursor.getLong(0);
byte[] secretKeyBlob = cursor.getBlob(1);
@ -473,8 +476,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
// db.execSQL("DROP TABLE keyrings_secret");
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
// Downgrade is ok for the debug version, makes it easier to work with branches
if (Constants.DEBUG) {
return;
@ -528,7 +530,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
public void clearDatabase() {
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS);
getWritableDatabase().execSQL("delete from " + Tables.API_APPS);
getWritableDatabase().execSQL("delete from api_apps");
}
}

View file

@ -23,6 +23,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@ -38,8 +39,6 @@ import android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
@ -71,10 +70,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
private static final int KEY_RING_LINKED_IDS = 207;
private static final int KEY_RING_LINKED_ID_CERTS = 208;
private static final int API_APPS = 301;
private static final int API_APPS_BY_PACKAGE_NAME = 302;
private static final int API_ALLOWED_KEYS = 305;
private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
private static final int KEY_RINGS_FIND_BY_USER_ID = 402;
@ -182,22 +177,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
+ KeychainContract.PATH_CERTS + "/*/*",
KEY_RING_CERTS_SPECIFIC);
/*
* API apps
*
* <pre>
* api_apps
* api_apps/_ (package name)
*
* api_apps/_/allowed_keys
* </pre>
*/
matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
+ KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS);
/*
* Trust Identity access
*
@ -269,15 +248,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
case KEY_SIGNATURES:
return KeySignatures.CONTENT_TYPE;
case API_APPS:
return ApiApps.CONTENT_TYPE;
case API_APPS_BY_PACKAGE_NAME:
return ApiApps.CONTENT_ITEM_TYPE;
case API_ALLOWED_KEYS:
return ApiAllowedKeys.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@ -781,26 +751,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
}
break;
}
case API_APPS: {
qb.setTables(Tables.API_APPS);
break;
}
case API_APPS_BY_PACKAGE_NAME: {
qb.setTables(Tables.API_APPS);
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
}
case API_ALLOWED_KEYS: {
qb.setTables(Tables.API_ALLOWED_KEYS);
qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAllowedKeys.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
}
default: {
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
}
@ -815,9 +765,10 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
orderBy = sortOrder;
}
SQLiteDatabase db = getDb().getReadableDatabase();
SupportSQLiteDatabase db = getDb().getReadableDatabase();
Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, orderBy);
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);
@ -844,7 +795,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
public Uri insert(Uri uri, ContentValues values) {
Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")");
final SQLiteDatabase db = getDb().getWritableDatabase();
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
Uri rowUri = null;
Long keyId = null;
@ -853,12 +804,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
switch (match) {
case KEY_RING_PUBLIC: {
db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values);
db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
break;
}
case KEY_RING_KEYS: {
db.insertOrThrow(Tables.KEYS, null, values);
db.insert(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(Keys.MASTER_KEY_ID);
break;
}
@ -873,46 +824,33 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
throw new AssertionError("Rank 0 user packet must be a user id!");
}
db.insertOrThrow(Tables.USER_PACKETS, null, values);
db.insert(Tables.USER_PACKETS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
break;
}
case KEY_RING_CERTS: {
// we replace here, keeping only the latest signature
// TODO this would be better handled in savePublicKeyRing directly!
db.replaceOrThrow(Tables.CERTS, null, values);
db.insert(Tables.CERTS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
break;
}
case UPDATED_KEYS: {
keyId = values.getAsLong(UpdatedKeys.MASTER_KEY_ID);
try {
db.insertOrThrow(Tables.UPDATED_KEYS, null, values);
db.insert(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values);
} catch (SQLiteConstraintException e) {
db.update(Tables.UPDATED_KEYS, values,
db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_IGNORE, values,
UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(keyId) });
}
rowUri = UpdatedKeys.CONTENT_URI;
break;
}
case KEY_SIGNATURES: {
db.insert(Tables.KEY_SIGNATURES, null, values);
db.insert(Tables.KEY_SIGNATURES, SQLiteDatabase.CONFLICT_FAIL, values);
rowUri = KeySignatures.CONTENT_URI;
break;
}
case API_APPS: {
db.insert(Tables.API_APPS, null, values);
break;
}
case API_ALLOWED_KEYS: {
// set foreign key automatically based on given uri
// e.g., api_apps/com.example.app/allowed_keys/
String packageName = uri.getPathSegments().get(1);
values.put(ApiAllowedKeys.PACKAGE_NAME, packageName);
db.insert(Tables.API_ALLOWED_KEYS, null, values);
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@ -937,7 +875,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
public int delete(Uri uri, String additionalSelection, String[] selectionArgs) {
Timber.v("delete(uri=" + uri + ")");
final SQLiteDatabase db = getDb().getWritableDatabase();
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
int count;
final int match = mUriMatcher.match(uri);
@ -980,16 +918,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs);
break;
case API_APPS_BY_PACKAGE_NAME: {
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection),
selectionArgs);
break;
}
case API_ALLOWED_KEYS: {
count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection),
selectionArgs);
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@ -1005,7 +933,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")");
final SQLiteDatabase db = getDb().getWritableDatabase();
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
int count = 0;
try {
@ -1022,12 +950,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
if (!TextUtils.isEmpty(selection)) {
actualSelection += " AND (" + selection + ")";
}
count = db.update(Tables.KEYS, values, actualSelection, selectionArgs);
break;
}
case API_APPS_BY_PACKAGE_NAME: {
count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, selection), selectionArgs);
count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs);
break;
}
case UPDATED_KEYS: {
@ -1040,7 +963,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
throw new UnsupportedOperationException("can only reset all keys");
}
db.update(Tables.UPDATED_KEYS, values, null, null);
db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values, null, null);
break;
}
case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: {
@ -1049,11 +972,11 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
values.put(ApiAutocryptPeer.PACKAGE_NAME, packageName);
values.put(ApiAutocryptPeer.IDENTIFIER, identifier);
int updated = db.update(Tables.API_AUTOCRYPT_PEERS, values,
int updated = db.update(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_IGNORE, values,
ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?",
new String[] { packageName, identifier });
if (updated == 0) {
db.insertOrThrow(Tables.API_AUTOCRYPT_PEERS, null, values);
db.insert(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_FAIL,values);
}
break;
@ -1068,35 +991,4 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
return count;
}
/**
* Build default selection statement for API apps. If no extra selection is specified only build
* where clause with rowId
*
* @param uri
* @param selection
* @return
*/
private String buildDefaultApiAppsSelection(Uri uri, String selection) {
String packageName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment());
String andSelection = "";
if (!TextUtils.isEmpty(selection)) {
andSelection = " AND (" + selection + ")";
}
return ApiApps.PACKAGE_NAME + "=" + packageName + andSelection;
}
private String buildDefaultApiAllowedKeysSelection(Uri uri, String selection) {
String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1));
String andSelection = "";
if (!TextUtils.isEmpty(selection)) {
andSelection = " AND (" + selection + ")";
}
return ApiAllowedKeys.PACKAGE_NAME + "=" + packageName + andSelection;
}
}

View file

@ -0,0 +1,105 @@
/*
* 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.provider;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.content.Context;
import okhttp3.internal.Util;
class LocalSecretKeyStorage {
private static final String FORMAT_STR_SECRET_KEY = "0x%016x.sec";
private static final String SECRET_KEYS_DIR_NAME = "secret_keys";
private final File localSecretKeysDir;
public static LocalSecretKeyStorage getInstance(Context context) {
File localSecretKeysDir = new File(context.getFilesDir(), SECRET_KEYS_DIR_NAME);
return new LocalSecretKeyStorage(localSecretKeysDir);
}
private LocalSecretKeyStorage(File localSecretKeysDir) {
this.localSecretKeysDir = localSecretKeysDir;
}
private File getSecretKeyFile(long masterKeyId) throws IOException {
if (!localSecretKeysDir.exists()) {
localSecretKeysDir.mkdir();
}
if (!localSecretKeysDir.isDirectory()) {
throw new IOException("Failed creating public key directory!");
}
String keyFilename = String.format(FORMAT_STR_SECRET_KEY, masterKeyId);
return new File(localSecretKeysDir, keyFilename);
}
void writeSecretKey(long masterKeyId, byte[] encoded) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
FileOutputStream fileOutputStream = new FileOutputStream(publicKeyFile);
try {
fileOutputStream.write(encoded);
} finally {
Util.closeQuietly(fileOutputStream);
}
}
byte[] readSecretKey(long masterKeyId) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
try {
FileInputStream fileInputStream = new FileInputStream(publicKeyFile);
return readIntoByteArray(fileInputStream);
} catch (FileNotFoundException e) {
return null;
}
}
private static byte[] readIntoByteArray(FileInputStream fileInputStream) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[128];
int bytesRead;
while ((bytesRead = fileInputStream.read(buf)) != -1) {
baos.write(buf, 0, bytesRead);
}
return baos.toByteArray();
}
void deleteSecretKey(long masterKeyId) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
if (publicKeyFile.exists()) {
boolean deleteSuccess = publicKeyFile.delete();
if (!deleteSuccess) {
throw new IOException("File exists, but could not be deleted!");
}
}
}
}

View file

@ -18,6 +18,9 @@
package org.sufficientlysecure.keychain.provider;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.db.SupportSQLiteQuery;
import android.arch.persistence.db.SupportSQLiteQueryBuilder;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -47,34 +50,31 @@ public class OverriddenWarningsRepository {
}
public boolean isWarningOverridden(String identifier) {
SQLiteDatabase db = getDb().getReadableDatabase();
Cursor cursor = db.query(
Tables.OVERRIDDEN_WARNINGS,
new String[] { "COUNT(*)" },
OverriddenWarnings.IDENTIFIER + " = ?",
new String[] { identifier },
null, null, null);
SupportSQLiteDatabase db = getDb().getReadableDatabase();
SupportSQLiteQuery query = SupportSQLiteQueryBuilder
.builder(Tables.OVERRIDDEN_WARNINGS)
.columns(new String[] { "COUNT(*) FROM " })
.selection(OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier })
.create();
Cursor cursor = db.query(query);
try {
cursor.moveToFirst();
return cursor.getInt(0) > 0;
} finally {
cursor.close();
db.close();
}
}
public void putOverride(String identifier) {
SQLiteDatabase db = getDb().getWritableDatabase();
SupportSQLiteDatabase db = getDb().getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(OverriddenWarnings.IDENTIFIER, identifier);
db.replace(Tables.OVERRIDDEN_WARNINGS, null, cv);
db.close();
db.insert(Tables.OVERRIDDEN_WARNINGS, SQLiteDatabase.CONFLICT_REPLACE, cv);
}
public void deleteOverride(String identifier) {
SQLiteDatabase db = getDb().getWritableDatabase();
SupportSQLiteDatabase db = getDb().getWritableDatabase();
db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier });
db.close();
}
}

View file

@ -138,7 +138,7 @@ public class ApiPendingIntentFactory {
PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, String preferredUserId) {
Intent intent = new Intent(mContext, SelectSignKeyIdActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
intent.putExtra(SelectSignKeyIdActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId);
return createInternal(data, intent);
@ -147,7 +147,6 @@ public class ApiPendingIntentFactory {
PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName,
byte[] packageSignature, String preferredUserId, boolean showAutocryptHint) {
Intent intent = new Intent(mContext, RemoteSelectIdKeyActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_USER_ID, preferredUserId);
@ -158,7 +157,6 @@ public class ApiPendingIntentFactory {
PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) {
Intent intent = new Intent(mContext, RemoteSelectAuthenticationKeyActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
intent.putExtra(RemoteSelectAuthenticationKeyActivity.EXTRA_PACKAGE_NAME, packageName);
return createInternal(data, intent);

View file

@ -1,50 +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.remote;
public class AppSettings {
private String mPackageName;
private byte[] mPackageCertificate;
public AppSettings() {
}
public AppSettings(String packageName, byte[] packageSignature) {
super();
this.mPackageName = packageName;
this.mPackageCertificate = packageSignature;
}
public String getPackageName() {
return mPackageName;
}
public void setPackageName(String packageName) {
this.mPackageName = packageName;
}
public byte[] getPackageCertificate() {
return mPackageCertificate;
}
public void setPackageCertificate(byte[] packageCertificate) {
this.mPackageCertificate = packageCertificate;
}
}

View file

@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@ -42,7 +43,6 @@ import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptRecommendationResult;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState;
import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
@ -54,7 +54,6 @@ import org.sufficientlysecure.keychain.provider.KeychainExternalContract.Autocry
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.provider.KeychainProvider;
import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface;
import org.sufficientlysecure.keychain.util.CloseDatabaseCursorFactory;
import timber.log.Timber;
@ -64,9 +63,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
private static final int AUTOCRYPT_STATUS = 201;
private static final int AUTOCRYPT_STATUS_INTERNAL = 202;
private static final int API_APPS = 301;
private static final int API_APPS_BY_PACKAGE_NAME = 302;
public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses";
public static final String TEMP_TABLE_COLUMN_ADDRES = "address";
@ -113,7 +109,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
internalKeychainProvider = new KeychainProvider();
internalKeychainProvider.attachInfo(context, null);
apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(internalKeychainProvider));
apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(getContext()));
databaseNotifyManager = DatabaseNotifyManager.create(context);
return true;
}
@ -127,13 +123,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
switch (match) {
case EMAIL_STATUS:
return EmailStatus.CONTENT_TYPE;
case API_APPS:
return ApiApps.CONTENT_TYPE;
case API_APPS_BY_PACKAGE_NAME:
return ApiApps.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@ -154,7 +143,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
String groupBy = null;
SQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase();
SupportSQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase();
String callingPackageName = apiPermissionHelper.getCurrentCallingPackage();
@ -169,7 +158,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
ContentValues cv = new ContentValues();
for (String address : selectionArgs) {
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv);
}
HashMap<String, String> projectionMap = new HashMap<>();
@ -256,7 +245,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
ContentValues cv = new ContentValues();
for (String address : selectionArgs) {
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv);
}
boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%");
@ -321,8 +310,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
}
qb.setStrict(true);
qb.setCursorFactory(new CloseDatabaseCursorFactory());
Cursor cursor = qb.query(db, projection, null, null, groupBy, null, orderBy);
String query = qb.buildQuery(projection, null, null, groupBy, null, orderBy);
Cursor cursor = db.query(query);
if (cursor != null) {
// Tell the cursor what uri to watch, so it knows when its source data changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
@ -337,7 +326,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
return cursor;
}
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) {
List<AutocryptRecommendationResult> autocryptStates =
autocryptPeerDao.determineAutocryptRecommendations(peerIds);
@ -345,7 +334,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
fillTempTableWithAutocryptRecommendations(db, autocryptStates);
}
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
List<AutocryptRecommendationResult> autocryptRecommendations) {
ContentValues cv = new ContentValues();
for (AutocryptRecommendationResult peerResult : autocryptRecommendations) {
@ -359,12 +348,12 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
KeychainExternalContract.KEY_STATUS_UNVERIFIED);
}
db.update(TEMP_TABLE_QUERIED_ADDRESSES, cv,TEMP_TABLE_COLUMN_ADDRES + "=?",
db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv,TEMP_TABLE_COLUMN_ADDRES + "=?",
new String[] { peerResult.peerId });
}
}
private void fillTempTableWithUidResult(SQLiteDatabase db, boolean isWildcardSelector) {
private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) {
String cmpOperator = isWildcardSelector ? " LIKE " : " = ";
long unixSeconds = System.currentTimeMillis() / 1000;
db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES +

View file

@ -915,8 +915,7 @@ public class OpenPgpService extends Service {
private HashSet<Long> getAllowedKeyIds() {
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
return mApiDao.getAllowedKeyIdsForApp(
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
return mApiDao.getAllowedKeyIdsForApp(currentPkg);
}
/**

View file

@ -17,12 +17,13 @@
package org.sufficientlysecure.keychain.remote;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
public class PackageUninstallReceiver extends BroadcastReceiver {
@ -34,8 +35,9 @@ public class PackageUninstallReceiver extends BroadcastReceiver {
return;
}
String packageName = uri.getEncodedSchemeSpecificPart();
Uri appUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName);
context.getContentResolver().delete(appUri, null, null);
ApiDataAccessObject apiDao = new ApiDataAccessObject(context);
apiDao.deleteApiApp(packageName);
}
}
}

View file

@ -17,11 +17,19 @@
package org.sufficientlysecure.keychain.remote;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.openintents.ssh.authentication.ISshAuthenticationService;
@ -40,7 +48,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ssh.AuthenticationData;
@ -50,13 +57,6 @@ import org.sufficientlysecure.keychain.ssh.AuthenticationResult;
import org.sufficientlysecure.keychain.ssh.signature.SshSignatureConverter;
import timber.log.Timber;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
public class SshAuthenticationService extends Service {
private static final String TAG = "SshAuthService";
@ -394,7 +394,7 @@ public class SshAuthenticationService extends Service {
private HashSet<Long> getAllowedKeyIds() {
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
return mApiDao.getAllowedKeyIdsForApp(KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
return mApiDao.getAllowedKeyIdsForApp(currentPkg);
}
/**

View file

@ -26,7 +26,6 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.view.Menu;
@ -36,24 +35,26 @@ import android.widget.TextView;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
import timber.log.Timber;
public class AppSettingsActivity extends BaseActivity {
private Uri mAppUri;
public static final String EXTRA_PACKAGE_NAME = "package_name";
private String packageName;
private TextView mAppNameView;
private ImageView mAppIconView;
// model
AppSettings mAppSettings;
ApiApp mApiApp;
private ApiDataAccessObject apiDataAccessObject;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -68,15 +69,16 @@ public class AppSettingsActivity extends BaseActivity {
setTitle(null);
Intent intent = getIntent();
mAppUri = intent.getData();
if (mAppUri == null) {
Timber.e("Intent data missing. Should be Uri of app!");
packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
if (packageName == null) {
Timber.e("Required extra package_name missing!");
finish();
return;
}
Timber.d("uri: %s", mAppUri);
loadData(savedInstanceState, mAppUri);
apiDataAccessObject = new ApiDataAccessObject(this);
loadData(savedInstanceState);
}
private void save() {
@ -138,7 +140,7 @@ public class AppSettingsActivity extends BaseActivity {
// advanced info: package certificate SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(mAppSettings.getPackageCertificate());
md.update(mApiApp.package_signature());
byte[] digest = md.digest();
certificate = new String(Hex.encode(digest));
} catch (NoSuchAlgorithmException e) {
@ -146,7 +148,7 @@ public class AppSettingsActivity extends BaseActivity {
}
AdvancedAppSettingsDialogFragment dialogFragment =
AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), certificate);
AdvancedAppSettingsDialogFragment.newInstance(mApiApp.package_name(), certificate);
dialogFragment.show(getSupportFragmentManager(), "advancedDialog");
}
@ -155,7 +157,7 @@ public class AppSettingsActivity extends BaseActivity {
Intent i;
PackageManager manager = getPackageManager();
try {
i = manager.getLaunchIntentForPackage(mAppSettings.getPackageName());
i = manager.getLaunchIntentForPackage(mApiApp.package_name());
if (i == null)
throw new PackageManager.NameNotFoundException();
// start like the Android launcher would do
@ -167,31 +169,29 @@ public class AppSettingsActivity extends BaseActivity {
}
}
private void loadData(Bundle savedInstanceState, Uri appUri) {
mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri);
private void loadData(Bundle savedInstanceState) {
mApiApp = apiDataAccessObject.getApiApp(packageName);
// get application name and icon from package manager
String appName;
Drawable appIcon = null;
PackageManager pm = getApplicationContext().getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0);
ApplicationInfo ai = pm.getApplicationInfo(mApiApp.package_name(), 0);
appName = (String) pm.getApplicationLabel(ai);
appIcon = pm.getApplicationIcon(ai);
} catch (PackageManager.NameNotFoundException e) {
// fallback
appName = mAppSettings.getPackageName();
appName = mApiApp.package_name();
}
mAppNameView.setText(appName);
mAppIconView.setImageDrawable(appIcon);
Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
Timber.d("allowedKeysUri: " + allowedKeysUri);
startListFragments(savedInstanceState, allowedKeysUri);
startListFragments(savedInstanceState);
}
private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) {
private void startListFragments(Bundle savedInstanceState) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
@ -199,7 +199,8 @@ public class AppSettingsActivity extends BaseActivity {
return;
}
AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri);
// Create an instance of the fragments
AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(packageName);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
@ -210,9 +211,7 @@ public class AppSettingsActivity extends BaseActivity {
}
private void revokeAccess() {
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
throw new RuntimeException();
}
apiDataAccessObject.deleteApiApp(packageName);
finish();
}

View file

@ -20,11 +20,9 @@ package org.sufficientlysecure.keychain.remote.ui;
import java.util.Set;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@ -40,25 +38,24 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
import timber.log.Timber;
public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String ARG_DATA_URI = "uri";
private static final String ARG_PACKAGE_NAME = "package_name";
private KeySelectableAdapter mAdapter;
private ApiDataAccessObject mApiDao;
private Uri mDataUri;
private String packageName;
/**
* Creates new instance of this fragment
*/
public static AppSettingsAllowedKeysListFragment newInstance(Uri dataUri) {
public static AppSettingsAllowedKeysListFragment newInstance(String packageName) {
AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
args.putString(ARG_PACKAGE_NAME, packageName);
frag.setArguments(args);
@ -101,13 +98,13 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
packageName = getArguments().getString(ARG_PACKAGE_NAME);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText(getString(R.string.list_empty));
Set<Long> checked = mApiDao.getAllowedKeyIdsForApp(mDataUri);
Set<Long> checked = mApiDao.getAllowedKeyIdsForApp(packageName);
mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);
setListAdapter(mAdapter);
getListView().setOnItemClickListener(mAdapter);
@ -140,11 +137,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
} */
public void saveAllowedKeys() {
try {
mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds());
} catch (RemoteException | OperationApplicationException e) {
Timber.e(e, "Problem saving allowed key ids!");
}
mApiDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds());
}
@Override

View file

@ -1,107 +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.remote.ui;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.remote.AppSettings;
import timber.log.Timber;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class AppSettingsHeaderFragment extends Fragment {
// model
private AppSettings mAppSettings;
// view
private TextView mAppNameView;
private ImageView mAppIconView;
private TextView mPackageName;
private TextView mPackageCertificate;
public AppSettings getAppSettings() {
return mAppSettings;
}
public void setAppSettings(AppSettings appSettings) {
this.mAppSettings = appSettings;
updateView(appSettings);
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
mAppNameView = view.findViewById(R.id.api_app_settings_app_name);
mAppIconView = view.findViewById(R.id.api_app_settings_app_icon);
mPackageName = view.findViewById(R.id.api_app_settings_package_name);
mPackageCertificate = view.findViewById(R.id.api_app_settings_package_certificate);
return view;
}
private void updateView(AppSettings appSettings) {
// get application name and icon from package manager
String appName;
Drawable appIcon = null;
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(appSettings.getPackageName(), 0);
appName = (String) pm.getApplicationLabel(ai);
appIcon = pm.getApplicationIcon(ai);
} catch (NameNotFoundException e) {
// fallback
appName = appSettings.getPackageName();
}
mAppNameView.setText(appName);
mAppIconView.setImageDrawable(appIcon);
// advanced info: package name
mPackageName.setText(appSettings.getPackageName());
// advanced info: package signature SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(appSettings.getPackageCertificate());
byte[] digest = md.digest();
String signature = new String(Hex.encode(digest));
mPackageCertificate.setText(signature);
} catch (NoSuchAlgorithmException e) {
Timber.e(e, "Should not happen!");
}
}
}

View file

@ -17,87 +17,77 @@
package org.sufficientlysecure.keychain.remote.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.CursorJoiner;
import android.database.MatrixCursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment.ApiAppAdapter;
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
import timber.log.Timber;
public class AppsListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {
AppsAdapter mAdapter;
public class AppsListFragment extends RecyclerFragment<ApiAppAdapter> {
private ApiAppAdapter adapter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getListView().setOnItemClickListener(this);
// NOTE: No setEmptyText(), we always have the default entries
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new AppsAdapter(getActivity(), null, 0);
setListAdapter(mAdapter);
adapter = new ApiAppAdapter(getActivity());
setAdapter(adapter);
setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
// NOTE: Loader is started in onResume!
new ApiAppsLiveData(getContext()).observe(this, this::onLoad);
}
@Override
public void onResume() {
super.onResume();
// Start out with a progress indicator.
setListShown(false);
// After coming back from Google Play -> reload
getLoaderManager().restartLoader(0, null, this);
private void onLoad(List<ListedApp> apiApps) {
if (apiApps == null) {
hideList(false);
adapter.setData(null);
return;
}
adapter.setData(apiApps);
showList(true);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String selectedPackageName = mAdapter.getItemPackageName(position);
boolean installed = mAdapter.getItemIsInstalled(position);
boolean registered = mAdapter.getItemIsRegistered(position);
public void onItemClick(int position) {
ListedApp listedApp = adapter.data.get(position);
if (installed) {
if (registered) {
if (listedApp.isInstalled) {
if (listedApp.isRegistered) {
// Edit app settings
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
intent.putExtra(AppSettingsActivity.EXTRA_PACKAGE_NAME, listedApp.packageName);
startActivity(intent);
} else {
Intent i;
PackageManager manager = getActivity().getPackageManager();
try {
i = manager.getLaunchIntentForPackage(selectedPackageName);
i = manager.getLaunchIntentForPackage(listedApp.packageName);
if (i == null) {
throw new PackageManager.NameNotFoundException();
}
@ -112,256 +102,163 @@ public class AppsListFragment extends ListFragment implements
} else {
try {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + selectedPackageName)));
Uri.parse("market://details?id=" + listedApp.packageName)));
} catch (ActivityNotFoundException anfe) {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("https://play.google.com/store/apps/details?id=" + selectedPackageName)));
Uri.parse("https://play.google.com/store/apps/details?id=" + listedApp.packageName)));
}
}
}
private static final String TEMP_COLUMN_NAME = "NAME";
private static final String TEMP_COLUMN_INSTALLED = "INSTALLED";
private static final String TEMP_COLUMN_REGISTERED = "REGISTERED";
private static final String TEMP_COLUMN_ICON_RES_ID = "ICON_RES_ID";
public class ApiAppAdapter extends Adapter<ApiAppViewHolder> {
private final LayoutInflater inflater;
static final String[] PROJECTION = new String[]{
ApiApps._ID, // 0
ApiApps.PACKAGE_NAME, // 1
"null as " + TEMP_COLUMN_NAME, // installed apps can retrieve app name from Android OS
"0 as " + TEMP_COLUMN_INSTALLED, // changed later in cursor joiner
"1 as " + TEMP_COLUMN_REGISTERED, // if it is in db it is registered
"0 as " + TEMP_COLUMN_ICON_RES_ID // not used
private List<ListedApp> data;
ApiAppAdapter(Context context) {
super();
inflater = LayoutInflater.from(context);
}
@Override
public ApiAppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ApiAppViewHolder(inflater.inflate(R.layout.api_apps_adapter_list_item, parent, false));
}
@Override
public void onBindViewHolder(ApiAppViewHolder holder, int position) {
ListedApp item = data.get(position);
holder.bind(item);
}
@Override
public int getItemCount() {
return data != null ? data.size() : 0;
}
public void setData(List<ListedApp> data) {
this.data = data;
notifyDataSetChanged();
}
}
public class ApiAppViewHolder extends RecyclerView.ViewHolder {
private final TextView text;
private final ImageView icon;
private final ImageView installIcon;
ApiAppViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.api_apps_adapter_item_name);
icon = itemView.findViewById(R.id.api_apps_adapter_item_icon);
installIcon = itemView.findViewById(R.id.api_apps_adapter_install_icon);
itemView.setOnClickListener((View view) -> onItemClick(getAdapterPosition()));
}
void bind(ListedApp listedApp) {
text.setText(listedApp.readableName);
if (listedApp.applicationIconRes != null) {
icon.setImageResource(listedApp.applicationIconRes);
} else {
icon.setImageDrawable(listedApp.applicationIcon);
}
installIcon.setVisibility(listedApp.isInstalled ? View.GONE : View.VISIBLE);
}
}
public static class ApiAppsLiveData extends AsyncTaskLiveData<List<ListedApp>> {
private final ApiDataAccessObject apiDao;
private final PackageManager packageManager;
ApiAppsLiveData(Context context) {
super(context, null);
packageManager = getContext().getPackageManager();
apiDao = new ApiDataAccessObject(context);
}
@Override
protected List<ListedApp> asyncLoadData() {
ArrayList<ListedApp> result = new ArrayList<>();
loadRegisteredApps(result);
addPlaceholderApps(result);
Collections.sort(result, (o1, o2) -> o1.readableName.compareTo(o2.readableName));
return result;
}
private void loadRegisteredApps(ArrayList<ListedApp> result) {
List<ApiApp> registeredApiApps = apiDao.getAllApiApps();
for (ApiApp apiApp : registeredApiApps) {
ListedApp listedApp;
try {
ApplicationInfo ai = packageManager.getApplicationInfo(apiApp.package_name(), 0);
CharSequence applicationLabel = packageManager.getApplicationLabel(ai);
Drawable applicationIcon = packageManager.getApplicationIcon(ai);
listedApp = new ListedApp(apiApp.package_name(), true, true, applicationLabel, applicationIcon, null);
} catch (PackageManager.NameNotFoundException e) {
listedApp = new ListedApp(apiApp.package_name(), false, true, apiApp.package_name(), null, null);
}
result.add(listedApp);
}
}
private void addPlaceholderApps(ArrayList<ListedApp> result) {
for (ListedApp placeholderApp : PLACERHOLDER_APPS) {
if (!containsByPackageName(result, placeholderApp.packageName)) {
try {
packageManager.getApplicationInfo(placeholderApp.packageName, 0);
result.add(placeholderApp.withIsInstalled());
} catch (PackageManager.NameNotFoundException e) {
result.add(placeholderApp);
}
}
}
}
private boolean containsByPackageName(ArrayList<ListedApp> result, String packageName) {
for (ListedApp app : result) {
if (packageName.equals(app.packageName)) {
return true;
}
}
return false;
}
}
public static class ListedApp {
final String packageName;
final boolean isInstalled;
final boolean isRegistered;
final String readableName;
final Drawable applicationIcon;
final Integer applicationIconRes;
ListedApp(String packageName, boolean isInstalled, boolean isRegistered, CharSequence readableName,
Drawable applicationIcon, Integer applicationIconRes) {
this.packageName = packageName;
this.isInstalled = isInstalled;
this.isRegistered = isRegistered;
this.readableName = readableName.toString();
this.applicationIcon = applicationIcon;
this.applicationIconRes = applicationIconRes;
}
public ListedApp withIsInstalled() {
return new ListedApp(packageName, true, isRegistered, readableName, applicationIcon, applicationIconRes);
}
}
private static final ListedApp[] PLACERHOLDER_APPS = {
new ListedApp("com.fsck.k9", false, false, "K-9 Mail", null, R.drawable.apps_k9),
new ListedApp("com.zeapo.pwdstore", false, false, "Password Store", null, R.drawable.apps_password_store),
new ListedApp("eu.siacs.conversations", false, false, "Conversations (Instant Messaging)", null,
R.drawable.apps_conversations)
};
private static final int INDEX_ID = 0;
private static final int INDEX_PACKAGE_NAME = 1;
private static final int INDEX_NAME = 2;
private static final int INDEX_INSTALLED = 3;
private static final int INDEX_REGISTERED = 4;
private static final int INDEX_ICON_RES_ID = 5;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri = ApiApps.CONTENT_URI;
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new AppsLoader(getActivity(), baseUri, PROJECTION, null, null,
ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
// The list should now be shown.
setListShown(true);
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
/**
* Besides the queried cursor with all registered apps, this loader also returns non-installed
* proposed apps using a MatrixCursor.
*/
private static class AppsLoader extends CursorLoader {
public AppsLoader(Context context) {
super(context);
}
public AppsLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
super(context, uri, projection, selection, selectionArgs, sortOrder);
}
@Override
public Cursor loadInBackground() {
// Load registered apps from content provider
Cursor data = super.loadInBackground();
MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{
ApiApps._ID,
ApiApps.PACKAGE_NAME,
TEMP_COLUMN_NAME,
TEMP_COLUMN_INSTALLED,
TEMP_COLUMN_REGISTERED,
TEMP_COLUMN_ICON_RES_ID
});
// NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner!
// Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png
availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9});
availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store});
availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations});
MatrixCursor mergedCursor = new MatrixCursor(new String[]{
ApiApps._ID,
ApiApps.PACKAGE_NAME,
TEMP_COLUMN_NAME,
TEMP_COLUMN_INSTALLED,
TEMP_COLUMN_REGISTERED,
TEMP_COLUMN_ICON_RES_ID
});
CursorJoiner joiner = new CursorJoiner(
availableAppsCursor,
new String[]{ApiApps.PACKAGE_NAME},
data,
new String[]{ApiApps.PACKAGE_NAME});
for (CursorJoiner.Result joinerResult : joiner) {
switch (joinerResult) {
case LEFT: {
// handle case where a row in availableAppsCursor is unique
String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME);
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
availableAppsCursor.getString(INDEX_NAME),
isInstalled(packageName),
0,
availableAppsCursor.getInt(INDEX_ICON_RES_ID)
});
break;
}
case RIGHT: {
// handle case where a row in data is unique
String packageName = data.getString(INDEX_PACKAGE_NAME);
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
null,
isInstalled(packageName),
1, // registered!
R.mipmap.ic_launcher // icon is retrieved later
});
break;
}
case BOTH: {
// handle case where a row with the same key is in both cursors
String packageName = data.getString(INDEX_PACKAGE_NAME);
String name;
if (isInstalled(packageName) == 1) {
name = data.getString(INDEX_NAME);
} else {
// if not installed take name from available apps list
name = availableAppsCursor.getString(INDEX_NAME);
}
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
name,
isInstalled(packageName),
1, // registered!
R.mipmap.ic_launcher // icon is retrieved later
});
break;
}
}
}
return mergedCursor;
}
private int isInstalled(String packageName) {
try {
getContext().getPackageManager().getApplicationInfo(packageName, 0);
return 1;
} catch (final PackageManager.NameNotFoundException e) {
return 0;
}
}
}
private class AppsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private PackageManager mPM;
public AppsAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
mPM = context.getApplicationContext().getPackageManager();
}
/**
* Similar to CursorAdapter.getItemId().
* Required to build Uris for api apps, which are not based on row ids
*/
public String getItemPackageName(int position) {
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getString(INDEX_PACKAGE_NAME);
} else {
return null;
}
}
public boolean getItemIsInstalled(int position) {
return mDataValid && mCursor != null
&& mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_INSTALLED) == 1);
}
public boolean getItemIsRegistered(int position) {
return mDataValid && mCursor != null
&& mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_REGISTERED) == 1);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView text = view.findViewById(R.id.api_apps_adapter_item_name);
ImageView icon = view.findViewById(R.id.api_apps_adapter_item_icon);
ImageView installIcon = view.findViewById(R.id.api_apps_adapter_install_icon);
String packageName = cursor.getString(INDEX_PACKAGE_NAME);
Timber.d("packageName: " + packageName);
int installed = cursor.getInt(INDEX_INSTALLED);
String name = cursor.getString(INDEX_NAME);
int iconResName = cursor.getInt(INDEX_ICON_RES_ID);
// get application name and icon
try {
ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
text.setText(mPM.getApplicationLabel(ai));
icon.setImageDrawable(mPM.getApplicationIcon(ai));
} catch (final PackageManager.NameNotFoundException e) {
// fallback
if (name == null) {
text.setText(packageName);
} else {
text.setText(name);
try {
icon.setImageDrawable(getResources().getDrawable(iconResName));
} catch (Resources.NotFoundException e1) {
// silently fail
}
}
}
if (installed == 1) {
installIcon.setVisibility(View.GONE);
} else {
installIcon.setVisibility(View.VISIBLE);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
}
}
}

View file

@ -26,8 +26,8 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.remote.AppSettings;
import timber.log.Timber;
@ -39,7 +39,7 @@ class RemoteRegisterPresenter {
private RemoteRegisterView view;
private Intent resultData;
private AppSettings appSettings;
private ApiApp apiApp;
RemoteRegisterPresenter(Context context) {
@ -54,7 +54,7 @@ class RemoteRegisterPresenter {
}
void setupFromIntentData(Intent resultData, String packageName, byte[] packageSignature) {
this.appSettings = new AppSettings(packageName, packageSignature);
this.apiApp = ApiApp.create(packageName, packageSignature);
this.resultData = resultData;
try {
@ -76,7 +76,7 @@ class RemoteRegisterPresenter {
}
void onClickAllow() {
apiDao.insertApiApp(appSettings);
apiDao.insertApiApp(apiApp);
view.finishWithResult(resultData);
}

View file

@ -34,6 +34,7 @@ import timber.log.Timber;
public class SelectSignKeyIdActivity extends BaseActivity {
public static final String EXTRA_PACKAGE_NAME = "package_name";
public static final String EXTRA_USER_ID = OpenPgpApi.EXTRA_USER_ID;
public static final String EXTRA_DATA = "data";
@ -68,19 +69,18 @@ public class SelectSignKeyIdActivity extends BaseActivity {
});
Intent intent = getIntent();
Uri appUri = intent.getData();
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID);
mData = intent.getParcelableExtra(EXTRA_DATA);
if (appUri == null) {
if (packageName == null) {
Timber.e("Intent data missing. Should be Uri of app!");
finish();
} else {
Timber.d("uri: " + appUri);
startListFragments(savedInstanceState, appUri, mData, mPreferredUserId);
startListFragments(savedInstanceState, packageName, mData, mPreferredUserId);
}
}
private void startListFragments(Bundle savedInstanceState, Uri dataUri, Intent data, String preferredUserId) {
private void startListFragments(Bundle savedInstanceState, String packageName, Intent data, String preferredUserId) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
@ -90,7 +90,7 @@ public class SelectSignKeyIdActivity extends BaseActivity {
// Create an instance of the fragments
SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment
.newInstance(dataUri, data, preferredUserId);
.newInstance(packageName, data, preferredUserId);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()

View file

@ -33,35 +33,33 @@ import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter;
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
import timber.log.Timber;
import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter;
public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyAdapter>
implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks<Cursor> {
private static final String ARG_DATA_URI = "uri";
private static final String ARG_PACKAGE_NAME = "package_name";
private static final String ARG_PREF_UID = "pref_uid";
public static final String ARG_DATA = "data";
private Uri mDataUri;
private Intent mResult;
private String mPrefUid;
private ApiDataAccessObject mApiDao;
private String mPackageName;
/**
* Creates new instance of this fragment
*/
public static SelectSignKeyIdListFragment newInstance(Uri dataUri, Intent data, String preferredUserId) {
public static SelectSignKeyIdListFragment newInstance(String packageName, Intent data, String preferredUserId) {
SelectSignKeyIdListFragment frag = new SelectSignKeyIdListFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
args.putString(ARG_PACKAGE_NAME, packageName);
args.putParcelable(ARG_DATA, data);
args.putString(ARG_PREF_UID, preferredUserId);
@ -85,7 +83,7 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyA
mResult = getArguments().getParcelable(ARG_DATA);
mPrefUid = getArguments().getString(ARG_PREF_UID);
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
mPackageName = getArguments().getString(ARG_PACKAGE_NAME);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
@ -175,16 +173,9 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyA
@Override
public void onSelectKeyItemClicked(long masterKeyId) {
Uri allowedKeysUri = mDataUri.buildUpon()
.appendPath(KeychainContract.PATH_ALLOWED_KEYS)
.build();
mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);
mApiDao.addAllowedKeyIdForApp(mPackageName, masterKeyId);
mResult.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId);
Timber.d("allowedKeyId: " + masterKeyId);
Timber.d("allowedKeysUri: " + allowedKeysUri);
getActivity().setResult(Activity.RESULT_OK, mResult);
getActivity().finish();
}

View file

@ -18,6 +18,8 @@
package org.sufficientlysecure.keychain.remote.ui.dialog;
import java.util.List;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
@ -27,7 +29,6 @@ import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
@ -48,21 +49,17 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.mikepenz.materialdrawer.util.KeyboardUtil;
import org.openintents.ssh.authentication.SshAuthenticationApi;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity;
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity;
import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyPresenter.RemoteSelectAuthenticationKeyView;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration;
import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
import java.util.List;
public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
public static final String EXTRA_PACKAGE_NAME = "package_name";
@ -71,6 +68,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
private RemoteSelectAuthenticationKeyPresenter presenter;
private String packageName;
@Override
@ -92,8 +90,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
super.onStart();
Intent intent = getIntent();
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
presenter.setupFromIntentData(packageName);
presenter.startLoaders(getSupportLoaderManager());
@ -104,14 +101,8 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
Intent originalIntent = callingIntent.getParcelableExtra(
RemoteSecurityTokenOperationActivity.EXTRA_DATA);
Uri appUri = callingIntent.getData();
Uri allowedKeysUri = appUri.buildUpon()
.appendPath(KeychainContract.PATH_ALLOWED_KEYS)
.build();
ApiDataAccessObject apiDao = new ApiDataAccessObject(getBaseContext());
apiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);
apiDao.addAllowedKeyIdForApp(packageName, masterKeyId);
originalIntent.putExtra(SshAuthenticationApi.EXTRA_KEY_ID, String.valueOf(masterKeyId));

View file

@ -34,12 +34,12 @@ import org.openintents.openpgp.util.OpenPgpUtils.UserId;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector;
import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import timber.log.Timber;
@ -58,7 +58,7 @@ class RemoteSelectIdentityKeyPresenter {
private long selectedMasterKeyId;
private byte[] generatedKeyData;
private ApiDataAccessObject apiDao;
private AppSettings appSettings;
private ApiApp apiApp;
RemoteSelectIdentityKeyPresenter(Context context, RemoteSelectIdViewModel viewModel, LifecycleOwner lifecycleOwner) {
@ -103,7 +103,7 @@ class RemoteSelectIdentityKeyPresenter {
Drawable appIcon = packageManager.getApplicationIcon(applicationInfo);
CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
appSettings = new AppSettings(packageName, packageSignature);
apiApp = ApiApp.create(packageName, packageSignature);
view.setTitleClientIconAndName(appIcon, appLabel);
}
@ -200,15 +200,15 @@ class RemoteSelectIdentityKeyPresenter {
}
void onHighlightFinished() {
apiDao.insertApiApp(appSettings);
apiDao.addAllowedKeyIdForApp(appSettings.getPackageName(), selectedMasterKeyId);
apiDao.insertApiApp(apiApp);
apiDao.addAllowedKeyIdForApp(apiApp.package_name(), selectedMasterKeyId);
view.finishAndReturn(selectedMasterKeyId);
}
void onImportOpSuccess(ImportKeyResult result) {
long importedMasterKeyId = result.getImportedMasterKeyIds()[0];
apiDao.insertApiApp(appSettings);
apiDao.addAllowedKeyIdForApp(appSettings.getPackageName(), selectedMasterKeyId);
apiDao.insertApiApp(apiApp);
apiDao.addAllowedKeyIdForApp(apiApp.package_name(), selectedMasterKeyId);
view.finishAndReturn(importedMasterKeyId);
}

View file

@ -17,8 +17,9 @@
package org.sufficientlysecure.keychain.util;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import timber.log.Timber;
@ -56,8 +57,8 @@ public class DatabaseUtil {
return result;
}
public static void explainQuery(SQLiteDatabase db, String sql) {
Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + sql, new String[0]);
public static void explainQuery(SupportSQLiteDatabase db, String sql) {
Cursor explainCursor = db.query("EXPLAIN QUERY PLAN " + sql, new String[0]);
// this is a debugging feature, we can be a little careless
explainCursor.moveToFirst();

View file

@ -0,0 +1,19 @@
CREATE TABLE IF NOT EXISTS api_allowed_keys (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
key_id INTEGER,
package_name TEXT NOT NULL,
UNIQUE (key_id, package_name),
FOREIGN KEY (package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE
);
insertAllowedKey:
INSERT INTO api_allowed_keys (package_name, key_id) VALUES (?, ?);
deleteByPackageName:
DELETE FROM api_allowed_keys
WHERE package_name = ?;
getAllowedKeys:
SELECT key_id
FROM api_allowed_keys
WHERE package_name = ?;

View file

@ -4,5 +4,23 @@ CREATE TABLE IF NOT EXISTS api_apps (
package_signature BLOB
);
getAllowedKeys:
SELECT
insertApiApp:
INSERT INTO api_apps (package_name, package_signature) VALUES (?, ?);
selectAll:
SELECT *
FROM api_apps;
selectByPackageName:
SELECT *
FROM api_apps
WHERE package_name = ?;
deleteByPackageName:
DELETE FROM api_apps
WHERE package_name = ?;
getCertificate:
SELECT package_signature
FROM api_apps
WHERE package_name = ?;

View file

@ -18,6 +18,7 @@ import org.robolectric.shadows.ShadowBinder;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowPackageManager;
import org.sufficientlysecure.keychain.KeychainTestRunner;
import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
@ -82,7 +83,7 @@ public class KeychainExternalProviderTest {
apiPermissionHelper = new ApiPermissionHelper(RuntimeEnvironment.application, apiDao);
autocryptPeerDao = new AutocryptPeerDataAccessObject(RuntimeEnvironment.application, PACKAGE_NAME);
apiDao.insertApiApp(new AppSettings(PACKAGE_NAME, PACKAGE_SIGNATURE));
apiDao.insertApiApp(new ApiApp(PACKAGE_NAME, PACKAGE_SIGNATURE));
}
@Test(expected = AccessControlException.class)
@ -99,7 +100,7 @@ public class KeychainExternalProviderTest {
@Test(expected = AccessControlException.class)
public void testPermission__withWrongPackageCert() throws Exception {
apiDao.deleteApiApp(PACKAGE_NAME);
apiDao.insertApiApp(new AppSettings(PACKAGE_NAME, new byte[] { 1, 2, 4 }));
apiDao.insertApiApp(new ApiApp(PACKAGE_NAME, new byte[] { 1, 2, 4 }));
contentResolver.query(
EmailStatus.CONTENT_URI,

View file

@ -10,6 +10,7 @@ buildscript {
classpath files('gradle-witness.jar')
// bintray dependency to satisfy dependency of openpgp-api lib
classpath 'com.novoda:bintray-release:0.8.0'
classpath 'com.squareup.sqldelight:gradle-plugin:0.7.0'
}
}