do not delete pub keys where secret key exists

This commit is contained in:
Dominik Schürmann 2014-02-06 01:02:18 +01:00
parent a41228fc06
commit 616c903a2a
6 changed files with 195 additions and 51 deletions

View File

@ -564,6 +564,26 @@ public class ProviderHelper {
return fingerprint; return fingerprint;
} }
public static String getUserId(Context context, Uri queryUri) {
String[] projection = new String[]{UserIds.USER_ID};
Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
String userId = null;
try {
if (cursor != null && cursor.moveToFirst()) {
int col = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
userId = cursor.getString(col);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return userId;
}
public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri, public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri,
long[] masterKeyIds) { long[] masterKeyIds) {
ArrayList<String> output = new ArrayList<String>(); ArrayList<String> output = new ArrayList<String>();

View File

@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
@ -37,6 +38,9 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
@ -51,6 +55,7 @@ import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Toast;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
@ -160,17 +165,15 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_key_list_public_multi_encrypt: { case R.id.menu_key_list_public_multi_encrypt: {
encrypt(ids); encrypt(mode, ids);
break; break;
} }
case R.id.menu_key_list_public_multi_delete: { case R.id.menu_key_list_public_multi_delete: {
showDeleteKeyDialog(ids); showDeleteKeyDialog(mode, ids);
break; break;
} }
} }
return false; return true;
} }
@Override @Override
@ -274,7 +277,7 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
startActivity(viewIntent); startActivity(viewIntent);
} }
public void encrypt(long[] keyRingRowIds) { public void encrypt(ActionMode mode, long[] keyRingRowIds) {
// get master key ids from row ids // get master key ids from row ids
long[] keyRingIds = new long[keyRingRowIds.length]; long[] keyRingIds = new long[keyRingRowIds.length];
for (int i = 0; i < keyRingRowIds.length; i++) { for (int i = 0; i < keyRingRowIds.length; i++) {
@ -286,6 +289,8 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingIds); intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingIds);
// used instead of startActivity set actionbar based on callingPackage // used instead of startActivity set actionbar based on callingPackage
startActivityForResult(intent, 0); startActivityForResult(intent, 0);
mode.finish();
} }
/** /**
@ -293,8 +298,34 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
* *
* @param keyRingRowIds * @param keyRingRowIds
*/ */
public void showDeleteKeyDialog(long[] keyRingRowIds) { public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) {
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null, // Message is received after key is deleted
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
Bundle returnData = message.getData();
if (returnData != null
&& returnData.containsKey(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED)) {
ArrayList<String> notDeleted =
returnData.getStringArrayList(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED);
String notDeletedMsg = "";
for (String userId : notDeleted) {
notDeletedMsg += userId + "\n";
}
Toast.makeText(getActivity(), getString(R.string.error_can_not_delete_contacts, notDeletedMsg),
Toast.LENGTH_LONG).show();
mode.finish();
}
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
keyRingRowIds, Id.type.public_key); keyRingRowIds, Id.type.public_key);
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");

View File

@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
@ -33,6 +34,9 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.ListFragment; import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
@ -45,6 +49,7 @@ import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.Toast;
public class KeyListSecretFragment extends ListFragment implements public class KeyListSecretFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener { LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {
@ -103,13 +108,12 @@ public class KeyListSecretFragment extends ListFragment implements
} }
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_key_list_public_multi_delete: { case R.id.menu_key_list_public_multi_delete: {
showDeleteKeyDialog(ids); showDeleteKeyDialog(mode, ids);
break;
break; }
} }
} return true;
return false;
} }
@Override @Override
@ -120,7 +124,7 @@ public class KeyListSecretFragment extends ListFragment implements
@Override @Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) { boolean checked) {
if (checked) { if (checked) {
count++; count++;
mAdapter.setNewSelection(position, checked); mAdapter.setNewSelection(position, checked);
@ -153,8 +157,8 @@ public class KeyListSecretFragment extends ListFragment implements
} }
// These are the rows that we will retrieve. // These are the rows that we will retrieve.
static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, static final String[] PROJECTION = new String[]{KeyRings._ID, KeyRings.MASTER_KEY_ID,
UserIds.USER_ID }; UserIds.USER_ID};
static final String SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC"; static final String SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC";
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
@ -202,13 +206,27 @@ public class KeyListSecretFragment extends ListFragment implements
/** /**
* Show dialog to delete key * Show dialog to delete key
* *
* @param keyRingRowIds * @param keyRingRowIds
*/ */
public void showDeleteKeyDialog(long[] keyRingRowIds) { public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) {
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null, // Message is received after key is deleted
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
mode.finish();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
keyRingRowIds, Id.type.secret_key); keyRingRowIds, Id.type.secret_key);
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
} }
} }

View File

@ -161,18 +161,7 @@ public class ViewKeyActivity extends ActionBarActivity implements
copyToClipboard(mDataUri); copyToClipboard(mDataUri);
return true; return true;
case R.id.menu_key_view_delete: { case R.id.menu_key_view_delete: {
// Message is received after key is deleted deleteKey(mDataUri);
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
setResult(RESULT_CANCELED);
finish();
}
}
};
mExportHelper.deleteKey(mDataUri, Id.type.public_key, returnHandler);
return true; return true;
} }
} }
@ -476,4 +465,27 @@ public class ViewKeyActivity extends ActionBarActivity implements
dialog.show(getSupportFragmentManager(), "shareNfcDialog"); dialog.show(getSupportFragmentManager(), "shareNfcDialog");
} }
private void deleteKey(Uri dataUri) {
// Message is received after key is deleted
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
Bundle returnData = message.getData();
if (returnData != null
&& returnData.containsKey(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED)) {
// we delete only this key, so MESSAGE_NOT_DELETED will solely contain this key
Toast.makeText(ViewKeyActivity.this, R.string.error_can_not_delete_contact,
Toast.LENGTH_LONG).show();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
}
};
mExportHelper.deleteKey(dataUri, Id.type.public_key, returnHandler);
}
} }

View File

@ -23,12 +23,16 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Message; import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
@ -36,6 +40,8 @@ import android.os.RemoteException;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import java.util.ArrayList;
public class DeleteKeyDialogFragment extends DialogFragment { public class DeleteKeyDialogFragment extends DialogFragment {
private static final String ARG_MESSENGER = "messenger"; private static final String ARG_MESSENGER = "messenger";
private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file"; private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file";
@ -43,13 +49,15 @@ public class DeleteKeyDialogFragment extends DialogFragment {
public static final int MESSAGE_OKAY = 1; public static final int MESSAGE_OKAY = 1;
public static final String MESSAGE_NOT_DELETED = "not_deleted";
private Messenger mMessenger; private Messenger mMessenger;
/** /**
* Creates new instance of this delete file dialog fragment * Creates new instance of this delete file dialog fragment
*/ */
public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds, public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds,
int keyType) { int keyType) {
DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
@ -77,20 +85,13 @@ public class DeleteKeyDialogFragment extends DialogFragment {
builder.setTitle(R.string.warning); builder.setTitle(R.string.warning);
if (keyRingRowIds.length == 1) { if (keyRingRowIds.length == 1) {
// TODO: better way to do this? Uri dataUri;
String userId = activity.getString(R.string.user_id_no_name);
if (keyType == Id.type.public_key) { if (keyType == Id.type.public_key) {
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByRowId(activity, dataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowIds[0]));
keyRingRowIds[0]);
userId = PgpKeyHelper.getMainUserIdSafe(activity,
PgpKeyHelper.getMasterKey(keyRing));
} else { } else {
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByRowId(activity, dataUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowIds[0]));
keyRingRowIds[0]);
userId = PgpKeyHelper.getMainUserIdSafe(activity,
PgpKeyHelper.getMasterKey(keyRing));
} }
String userId = ProviderHelper.getUserId(activity, dataUri);
builder.setMessage(getString( builder.setMessage(getString(
keyType == Id.type.public_key ? R.string.key_deletion_confirmation keyType == Id.type.public_key ? R.string.key_deletion_confirmation
@ -104,9 +105,61 @@ public class DeleteKeyDialogFragment extends DialogFragment {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
ArrayList<String> notDeleted = new ArrayList<String>();
if (keyType == Id.type.public_key) { if (keyType == Id.type.public_key) {
for (long keyRowId : keyRingRowIds) { Uri queryUri = KeychainContract.KeyRings.buildPublicKeyRingsUri();
ProviderHelper.deletePublicKeyRing(activity, keyRowId); String[] projection = new String[]{
KeychainContract.KeyRings._ID, // 0
KeychainContract.KeyRings.MASTER_KEY_ID, // 1
KeychainContract.UserIds.USER_ID // 2
};
// make selection with all entries where _ID is one of the given row ids
String selection = KeychainDatabase.Tables.KEY_RINGS + "." +
KeychainContract.KeyRings._ID + " IN(";
String selectionIDs = "";
for (int i = 0; i < keyRingRowIds.length; i++) {
selectionIDs += "'" + String.valueOf(keyRingRowIds[i]) + "'";
if (i+1 < keyRingRowIds.length)
selectionIDs += ",";
}
selection += selectionIDs + ")";
Cursor cursor = activity.getContentResolver().query(queryUri, projection,
selection, null, null);
long rowId;
long masterKeyId;
String userId;
try {
while (cursor != null && cursor.moveToNext()) {
rowId = cursor.getLong(0);
masterKeyId = cursor.getLong(1);
userId = cursor.getString(2);
Log.d(Constants.TAG, "rowId: " + rowId + ", masterKeyId: " + masterKeyId
+ ", userId: " + userId);
// check if a corresponding secret key exists...
Cursor secretCursor = activity.getContentResolver().query(
KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(String.valueOf(masterKeyId)),
null, null, null, null
);
if (secretCursor != null && secretCursor.getCount() > 0) {
notDeleted.add(userId);
} else {
// it is okay to delete this key, no secret key found!
ProviderHelper.deletePublicKeyRing(activity, rowId);
}
if (secretCursor != null) {
secretCursor.close();
}
}
} finally {
if (cursor != null) {
cursor.close();
}
} }
} else { } else {
for (long keyRowId : keyRingRowIds) { for (long keyRowId : keyRingRowIds) {
@ -116,7 +169,13 @@ public class DeleteKeyDialogFragment extends DialogFragment {
dismiss(); dismiss();
sendMessageToHandler(MESSAGE_OKAY); if (notDeleted.size() > 0) {
Bundle data = new Bundle();
data.putStringArrayList(MESSAGE_NOT_DELETED, notDeleted);
sendMessageToHandler(MESSAGE_OKAY, data);
} else {
sendMessageToHandler(MESSAGE_OKAY, null);
}
} }
}); });
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@ -131,13 +190,15 @@ public class DeleteKeyDialogFragment extends DialogFragment {
/** /**
* Send message back to handler which is initialized in a activity * Send message back to handler which is initialized in a activity
* *
* @param what * @param what Message integer you want to send
* Message integer you want to send
*/ */
private void sendMessageToHandler(Integer what) { private void sendMessageToHandler(Integer what, Bundle data) {
Message msg = Message.obtain(); Message msg = Message.obtain();
msg.what = what; msg.what = what;
if (data != null) {
msg.setData(data);
}
try { try {
mMessenger.send(msg); mMessenger.send(msg);

View File

@ -283,6 +283,8 @@
<string name="error_nfc_needed">NFC is not available on your device!</string> <string name="error_nfc_needed">NFC is not available on your device!</string>
<string name="error_nothing_import">Nothing to import!</string> <string name="error_nothing_import">Nothing to import!</string>
<string name="error_expiry_must_come_after_creation">expiry date must come after creation date</string> <string name="error_expiry_must_come_after_creation">expiry date must come after creation date</string>
<string name="error_can_not_delete_contact">you can not delete this contact because it is your own. Please delete it from the \'My Keys\' screen!</string>
<string name="error_can_not_delete_contacts">you can not delete the following contacts because they are your own:\n%sPlease delete them from the \'My Keys\' screen!</string>
<!-- progress dialogs, usually ending in '…' --> <!-- progress dialogs, usually ending in '…' -->
<string name="progress_done">done.</string> <string name="progress_done">done.</string>