mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-16 15:02:54 +00:00
Merge pull request #1014 from orange-elephant/export-selected-groups
Allow exporting specific groups
This commit is contained in:
commit
fd5a0390f0
5 changed files with 165 additions and 14 deletions
|
@ -15,6 +15,7 @@ import android.widget.Toast;
|
|||
|
||||
import androidx.annotation.ArrayRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.preference.Preference;
|
||||
|
@ -32,6 +33,7 @@ import com.beemdevelopment.aegis.ui.TransferEntriesActivity;
|
|||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||
import com.beemdevelopment.aegis.ui.tasks.ExportTask;
|
||||
import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
|
||||
import com.beemdevelopment.aegis.vault.Vault;
|
||||
import com.beemdevelopment.aegis.vault.VaultBackupManager;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
|
@ -45,15 +47,18 @@ import java.io.FileOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||
// keep a reference to the type of database converter that was selected
|
||||
private DatabaseImporter.Definition _importerDef;
|
||||
private Vault.EntryFilter _exportFilter;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
|
@ -153,6 +158,9 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
TextView warningText = view.findViewById(R.id.text_export_warning);
|
||||
CheckBox checkBoxEncrypt = view.findViewById(R.id.checkbox_export_encrypt);
|
||||
CheckBox checkBoxAccept = view.findViewById(R.id.checkbox_accept);
|
||||
CheckBox checkBoxExportAllGroups = view.findViewById(R.id.export_selected_groups);
|
||||
LinearLayout groupsSelection = view.findViewById(R.id.select_groups);
|
||||
TextView groupsSelectionDescriptor = view.findViewById(R.id.select_groups_hint);
|
||||
TextView passwordInfoText = view.findViewById(R.id.text_separate_password);
|
||||
passwordInfoText.setVisibility(checkBoxEncrypt.isChecked() && isBackupPasswordSet ? View.VISIBLE : View.GONE);
|
||||
AutoCompleteTextView dropdown = view.findViewById(R.id.dropdown_export_format);
|
||||
|
@ -165,6 +173,13 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
passwordInfoText.setVisibility(checkBoxEncrypt.isChecked() && isBackupPasswordSet ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
|
||||
for (String group: _vaultManager.getVault().getGroups()) {
|
||||
CheckBox box = new CheckBox(requireContext());
|
||||
box.setText(group);
|
||||
box.setChecked(false);
|
||||
groupsSelection.addView(box);
|
||||
}
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.pref_export_summary)
|
||||
.setView(view)
|
||||
|
@ -191,6 +206,12 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
btnNeutral.setEnabled(isChecked);
|
||||
});
|
||||
|
||||
checkBoxExportAllGroups.setOnCheckedChangeListener((button, isChecked) -> {
|
||||
int visibility = isChecked ? View.GONE : View.VISIBLE;
|
||||
groupsSelection.setVisibility(visibility);
|
||||
groupsSelectionDescriptor.setVisibility(visibility);
|
||||
});
|
||||
|
||||
btnPos.setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
|
||||
|
@ -198,6 +219,16 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!checkBoxExportAllGroups.isChecked()) {
|
||||
_exportFilter = getVaultEntryFilter(groupsSelection);
|
||||
if (_exportFilter == null) {
|
||||
Toast noGroupsSelected = new Toast(requireContext());
|
||||
noGroupsSelected.setText(R.string.export_no_groups_selected);
|
||||
noGroupsSelected.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int pos = getStringResourceIndex(R.array.export_formats, dropdown.getText().toString());
|
||||
int requestCode = getExportRequestCode(pos, checkBoxEncrypt.isChecked());
|
||||
VaultBackupManager.FileInfo fileInfo = getExportFileInfo(pos, checkBoxEncrypt.isChecked());
|
||||
|
@ -205,6 +236,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType(getExportMimeType(requestCode))
|
||||
.putExtra(Intent.EXTRA_TITLE, fileInfo.toString());
|
||||
|
||||
_vaultManager.startActivityForResult(this, intent, requestCode);
|
||||
});
|
||||
|
||||
|
@ -216,6 +248,16 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!checkBoxExportAllGroups.isChecked()) {
|
||||
_exportFilter = getVaultEntryFilter(groupsSelection);
|
||||
if (_exportFilter == null) {
|
||||
Toast noGroupsSelected = new Toast(requireContext());
|
||||
noGroupsSelected.setText(R.string.export_no_groups_selected);
|
||||
noGroupsSelected.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
File file;
|
||||
try {
|
||||
VaultBackupManager.FileInfo fileInfo = getExportFileInfo(pos, checkBoxEncrypt.isChecked());
|
||||
|
@ -246,13 +288,28 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
Intent chooser = Intent.createChooser(intent, getString(R.string.pref_export_summary));
|
||||
_vaultManager.startActivity(this, chooser);
|
||||
});
|
||||
}, _exportFilter);
|
||||
_exportFilter = null;
|
||||
});
|
||||
});
|
||||
|
||||
Dialogs.showSecureDialog(dialog);
|
||||
}
|
||||
|
||||
private Vault.EntryFilter getVaultEntryFilter(LinearLayout view) {
|
||||
Set<String> groups = new HashSet<>();
|
||||
for (int i=0; i<view.getChildCount(); i++) {
|
||||
CheckBox group = (CheckBox) view.getChildAt(i);
|
||||
if (group.isChecked() && group.getText().toString().equals(getString(R.string.no_group))) {
|
||||
groups.add(null);
|
||||
} else if (group.isChecked()) {
|
||||
groups.add(group.getText().toString());
|
||||
}
|
||||
}
|
||||
|
||||
return groups.isEmpty() ? null : entry -> groups.contains(entry.getGroup());
|
||||
}
|
||||
|
||||
private void startGoogleAuthenticatorStyleExport() {
|
||||
ArrayList<GoogleAuthInfo> toExport = new ArrayList<>();
|
||||
for (VaultEntry entry : _vaultManager.getVault().getEntries()) {
|
||||
|
@ -326,11 +383,17 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
return dir;
|
||||
}
|
||||
|
||||
private void startExportVault(int requestCode, StartExportCallback cb) {
|
||||
private void startExportVault(int requestCode, StartExportCallback cb, @Nullable Vault.EntryFilter filter) {
|
||||
switch (requestCode) {
|
||||
case CODE_EXPORT:
|
||||
if (_vaultManager.getVault().isEncryptionEnabled()) {
|
||||
cb.exportVault(stream -> _vaultManager.getVault().export(stream));
|
||||
cb.exportVault(stream -> {
|
||||
if (filter != null) {
|
||||
_vaultManager.getVault().exportFiltered(stream, filter);
|
||||
} else {
|
||||
_vaultManager.getVault().export(stream);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Dialogs.showSetPasswordDialog(requireActivity(), new Dialogs.PasswordSlotListener() {
|
||||
@Override
|
||||
|
@ -345,7 +408,13 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
cb.exportVault(stream -> _vaultManager.getVault().export(stream, creds));
|
||||
cb.exportVault(stream -> {
|
||||
if (filter != null) {
|
||||
_vaultManager.getVault().exportFiltered(stream, creds, filter);
|
||||
} else {
|
||||
_vaultManager.getVault().export(stream, creds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -356,11 +425,18 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
}
|
||||
break;
|
||||
case CODE_EXPORT_PLAIN:
|
||||
cb.exportVault((stream) -> _vaultManager.getVault().export(stream, null));
|
||||
cb.exportVault(stream -> {
|
||||
if (filter != null) {
|
||||
_vaultManager.getVault().exportFiltered(stream, null, filter);
|
||||
} else {
|
||||
_vaultManager.getVault().export(stream, null);
|
||||
}
|
||||
});
|
||||
|
||||
_prefs.setIsPlaintextBackupWarningNeeded(true);
|
||||
break;
|
||||
case CODE_EXPORT_GOOGLE_URI:
|
||||
cb.exportVault((stream) -> _vaultManager.getVault().exportGoogleUris(stream));
|
||||
cb.exportVault((stream) -> _vaultManager.getVault().exportGoogleUris(stream, filter));
|
||||
_prefs.setIsPlaintextBackupWarningNeeded(true);
|
||||
break;
|
||||
}
|
||||
|
@ -393,7 +469,8 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, _exportFilter);
|
||||
_exportFilter = null;
|
||||
}
|
||||
|
||||
private int getStringResourceIndex(@ArrayRes int id, String string) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
@ -11,10 +13,16 @@ public class Vault {
|
|||
private UUIDMap<VaultEntry> _entries = new UUIDMap<>();
|
||||
|
||||
public JSONObject toJson() {
|
||||
return toJson(null);
|
||||
}
|
||||
|
||||
public JSONObject toJson(@Nullable EntryFilter filter) {
|
||||
try {
|
||||
JSONArray array = new JSONArray();
|
||||
for (VaultEntry e : _entries) {
|
||||
array.put(e.toJson());
|
||||
if (filter == null || filter.includeEntry(e)) {
|
||||
array.put(e.toJson());
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject obj = new JSONObject();
|
||||
|
@ -51,4 +59,8 @@ public class Vault {
|
|||
public UUIDMap<VaultEntry> getEntries() {
|
||||
return _entries;
|
||||
}
|
||||
|
||||
public interface EntryFilter {
|
||||
boolean includeEntry(VaultEntry entry);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,17 +141,36 @@ public class VaultRepository {
|
|||
* Exports the vault by serializing it and writing it to the given OutputStream. If creds is
|
||||
* not null, it will be used to encrypt the vault first.
|
||||
*/
|
||||
public void export(OutputStream stream, VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
public void export(OutputStream stream, @Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
exportFiltered(stream, creds, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the vault by serializing it and writing it to the given OutputStream. If encryption
|
||||
* is enabled, the vault will be encrypted automatically. If filter is not null only specified
|
||||
* entries will be exported
|
||||
*/
|
||||
public void exportFiltered(OutputStream stream, @Nullable Vault.EntryFilter filter) throws VaultRepositoryException {
|
||||
exportFiltered(stream, getCredentials(), filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the vault by serializing it and writing it to the given OutputStream. If creds is
|
||||
* not null, it will be used to encrypt the vault first. If filter is not null only specified
|
||||
* entries will be exported
|
||||
*/
|
||||
public void exportFiltered(OutputStream stream, @Nullable VaultFileCredentials creds, @Nullable Vault.EntryFilter filter) throws VaultRepositoryException {
|
||||
if (creds != null) {
|
||||
creds = creds.exportable();
|
||||
}
|
||||
|
||||
try {
|
||||
VaultFile vaultFile = new VaultFile();
|
||||
|
||||
if (creds != null) {
|
||||
vaultFile.setContent(_vault.toJson(), creds);
|
||||
vaultFile.setContent(_vault.toJson(filter), creds);
|
||||
} else {
|
||||
vaultFile.setContent(_vault.toJson());
|
||||
vaultFile.setContent(_vault.toJson(filter));
|
||||
}
|
||||
|
||||
byte[] bytes = vaultFile.toBytes();
|
||||
|
@ -165,11 +184,13 @@ public class VaultRepository {
|
|||
* Exports the vault by serializing the list of entries to a newline-separated list of
|
||||
* Google Authenticator URI's and writing it to the given OutputStream.
|
||||
*/
|
||||
public void exportGoogleUris(OutputStream outStream) throws VaultRepositoryException {
|
||||
public void exportGoogleUris(OutputStream outStream, @Nullable Vault.EntryFilter filter) throws VaultRepositoryException {
|
||||
try (PrintStream stream = new PrintStream(outStream, false, StandardCharsets.UTF_8.name())) {
|
||||
for (VaultEntry entry : getEntries()) {
|
||||
GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
|
||||
stream.println(info.getUri().toString());
|
||||
if (filter == null || filter.includeEntry(entry)) {
|
||||
GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
|
||||
stream.println(info.getUri().toString());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new VaultRepositoryException(e);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue