Merge pull request #278 from alexbakker/scoped-storage

Rewrite the export functionality to use the Storage Access Framework
pull/285/head
Michael Schättgen 6 years ago committed by GitHub
commit eedf9d2935
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,8 +19,7 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme.NoActionBar" android:theme="@style/AppTheme.NoActionBar"
tools:ignore="GoogleAppIndexingWarning" tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:theme" tools:replace="android:theme">
android:requestLegacyExternalStorage="true">
<activity <activity
android:name=".ui.AboutActivity" android:name=".ui.AboutActivity"
android:label="@string/title_activity_about"></activity> android:label="@string/title_activity_about"></activity>

@ -5,7 +5,6 @@ import android.app.Activity;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.Window; import android.view.Window;
@ -26,16 +25,6 @@ import com.beemdevelopment.aegis.Theme;
import com.beemdevelopment.aegis.ViewMode; import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle; import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException; import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
import com.beemdevelopment.aegis.vault.VaultFileException;
import com.beemdevelopment.aegis.vault.VaultManager;
import com.beemdevelopment.aegis.vault.VaultManagerException;
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
import com.beemdevelopment.aegis.vault.slots.Slot;
import com.beemdevelopment.aegis.vault.slots.SlotException;
import com.beemdevelopment.aegis.vault.slots.SlotList;
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer; import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
import com.beemdevelopment.aegis.helpers.BiometricsHelper; import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.beemdevelopment.aegis.helpers.PermissionHelper; import com.beemdevelopment.aegis.helpers.PermissionHelper;
@ -47,6 +36,16 @@ import com.beemdevelopment.aegis.services.NotificationService;
import com.beemdevelopment.aegis.ui.models.ImportEntry; import com.beemdevelopment.aegis.ui.models.ImportEntry;
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference; import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
import com.beemdevelopment.aegis.util.UUIDMap; import com.beemdevelopment.aegis.util.UUIDMap;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
import com.beemdevelopment.aegis.vault.VaultFileException;
import com.beemdevelopment.aegis.vault.VaultManager;
import com.beemdevelopment.aegis.vault.VaultManagerException;
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
import com.beemdevelopment.aegis.vault.slots.Slot;
import com.beemdevelopment.aegis.vault.slots.SlotException;
import com.beemdevelopment.aegis.vault.slots.SlotList;
import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile; import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream; import com.topjohnwu.superuser.io.SuFileInputStream;
@ -54,6 +53,7 @@ import com.topjohnwu.superuser.io.SuFileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -70,6 +70,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
private static final int CODE_SLOTS = 2; private static final int CODE_SLOTS = 2;
private static final int CODE_GROUPS = 3; private static final int CODE_GROUPS = 3;
private static final int CODE_SELECT_ENTRIES = 4; private static final int CODE_SELECT_ENTRIES = 4;
private static final int CODE_EXPORT = 5;
private static final int CODE_EXPORT_ENCRYPT = 6;
// permission request codes // permission request codes
private static final int CODE_PERM_IMPORT = 0; private static final int CODE_PERM_IMPORT = 0;
@ -406,6 +408,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
case CODE_SELECT_ENTRIES: case CODE_SELECT_ENTRIES:
onSelectEntriesResult(resultCode, data); onSelectEntriesResult(resultCode, data);
break; break;
case CODE_EXPORT:
// intentional fallthrough
case CODE_EXPORT_ENCRYPT:
onExportResult(resultCode, data, requestCode == CODE_EXPORT_ENCRYPT);
break;
} }
} }
@ -587,18 +594,12 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setTitle(R.string.pref_export_summary) .setTitle(R.string.pref_export_summary)
.setPositiveButton(android.R.string.ok, (dialog, which) -> { .setPositiveButton(android.R.string.ok, (dialog, which) -> {
String filename; Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
try { .addCategory(Intent.CATEGORY_OPENABLE)
filename = _vault.export(checked.get()); .setType("application/json")
} catch (VaultManagerException e) { .putExtra(Intent.EXTRA_TITLE, checked.get() ? VaultManager.FILENAME_EXPORT : VaultManager.FILENAME_EXPORT_PLAIN);
Toast.makeText(getActivity(), R.string.exporting_vault_error, Toast.LENGTH_SHORT).show();
return;
}
// make sure the new file is visible
MediaScannerConnection.scanFile(getActivity(), new String[]{filename}, null, null);
Toast.makeText(getActivity(), getString(R.string.export_vault_location) + filename, Toast.LENGTH_SHORT).show(); startActivityForResult(intent, checked.get() ? CODE_EXPORT_ENCRYPT : CODE_EXPORT);
}) })
.setNegativeButton(android.R.string.cancel, null); .setNegativeButton(android.R.string.cancel, null);
if (_vault.isEncryptionEnabled()) { if (_vault.isEncryptionEnabled()) {
@ -669,6 +670,22 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
_result.putExtra("needsRecreate", true); _result.putExtra("needsRecreate", true);
} }
private void onExportResult(int resultCode, Intent data, boolean encrypt) {
Uri uri = data.getData();
if (resultCode != Activity.RESULT_OK || uri == null) {
return;
}
try (OutputStream stream = getContext().getContentResolver().openOutputStream(uri, "w")) {
_vault.export(stream, encrypt);
} catch (IOException | VaultManagerException e) {
Toast.makeText(getActivity(), R.string.exporting_vault_error, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(getActivity(), getString(R.string.exported_vault), Toast.LENGTH_SHORT).show();
}
private boolean saveVault() { private boolean saveVault() {
try { try {
_vault.save(); _vault.save();

@ -2,10 +2,7 @@ package com.beemdevelopment.aegis.vault;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Environment;
import com.beemdevelopment.aegis.BuildConfig;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.services.NotificationService; import com.beemdevelopment.aegis.services.NotificationService;
import org.json.JSONObject; import org.json.JSONObject;
@ -15,14 +12,15 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.text.Collator; import java.text.Collator;
import java.util.Collection; import java.util.Collection;
import java.util.TreeSet; import java.util.TreeSet;
public class VaultManager { public class VaultManager {
private static final String FILENAME = "aegis.json"; private static final String FILENAME = "aegis.json";
private static final String FILENAME_EXPORT = "aegis_export.json"; public static final String FILENAME_EXPORT = "aegis_export";
private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json"; public static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain";
private Vault _vault; private Vault _vault;
private VaultFile _file; private VaultFile _file;
@ -104,7 +102,7 @@ public class VaultManager {
} }
} }
public String export(boolean encrypt) throws VaultManagerException { public void export(OutputStream stream, boolean encrypt) throws VaultManagerException {
assertState(false, true); assertState(false, true);
try { try {
@ -115,19 +113,8 @@ public class VaultManager {
vaultFile.setContent(_vault.toJson()); vaultFile.setContent(_vault.toJson());
} }
String dirName = !BuildConfig.DEBUG ? _context.getString(R.string.app_name) : _context.getString(R.string.app_name_dev);
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("error creating external storage directory");
}
byte[] bytes = vaultFile.toBytes(); byte[] bytes = vaultFile.toBytes();
File file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN); stream.write(bytes);
try (FileOutputStream stream = new FileOutputStream(file)) {
stream.write(bytes);
}
return file.getAbsolutePath();
} catch (IOException | VaultFileException e) { } catch (IOException | VaultFileException e) {
throw new VaultManagerException(e); throw new VaultManagerException(e);
} }

@ -127,7 +127,7 @@
<string name="read_entries_count">Read %d entries. %d errors.</string> <string name="read_entries_count">Read %d entries. %d errors.</string>
<string name="import_error_title">One or more errors occurred during the import</string> <string name="import_error_title">One or more errors occurred during the import</string>
<string name="exporting_vault_error">An error occurred while trying to export the vault</string> <string name="exporting_vault_error">An error occurred while trying to export the vault</string>
<string name="export_vault_location">The vault has been exported to:</string> <string name="exported_vault">The vault has been exported</string>
<string name="export_warning">This action will export the vault out of Aegis\' private storage.</string> <string name="export_warning">This action will export the vault out of Aegis\' private storage.</string>
<string name="encryption_set_password_error">An error occurred while trying to set the password: </string> <string name="encryption_set_password_error">An error occurred while trying to set the password: </string>
<string name="encryption_enable_biometrics_error">An error occurred while trying to enable biometric unlock</string> <string name="encryption_enable_biometrics_error">An error occurred while trying to enable biometric unlock</string>

Loading…
Cancel
Save