use SQLDelight, remove ApiApps access from KeychainProvider
This commit is contained in:
parent
59c9f52e85
commit
d133b732e5
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 +
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 = ?;
|
|
@ -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 = ?;
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue