diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java index 7f2638e3..daf8d416 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -13,6 +13,7 @@ import com.topjohnwu.superuser.io.SuFileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; @@ -59,6 +60,10 @@ public abstract class DatabaseImporter { return new SuFile(man.getApplicationInfo(pkgName, 0).dataDir, subPath); } + public boolean isInstalledAppVersionSupported() { + return true; + } + protected abstract State read(InputStream stream, boolean isInternal) throws DatabaseImporterException; public State read(InputStream stream) throws DatabaseImporterException { @@ -91,7 +96,7 @@ public abstract class DatabaseImporter { return Collections.unmodifiableList(_importers); } - public static class Definition { + public static class Definition implements Serializable { private final String _name; private final Class _type; private final @StringRes int _help; diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java index 58cbc040..4070bcbb 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java @@ -1,6 +1,7 @@ package com.beemdevelopment.aegis.importers; import android.content.Context; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.database.Cursor; @@ -32,7 +33,19 @@ public class GoogleAuthImporter extends DatabaseImporter { protected SuFile getAppPath() throws PackageManager.NameNotFoundException { return getAppPath(_pkgName, _subPath); } - + + @Override + public boolean isInstalledAppVersionSupported() { + PackageInfo info; + try { + info = requireContext().getPackageManager().getPackageInfo(_pkgName, 0); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + + return info.versionCode <= 5000100; + } + @Override public State read(InputStream stream, boolean isInternal) throws DatabaseImporterException { final Context context = requireContext(); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java index 02399c76..f8a1e89e 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java @@ -83,21 +83,35 @@ public class ImportEntriesActivity extends AegisActivity { }); _fabScrollHelper = new FabScrollHelper(fab); - Class importerType = (Class) getIntent().getSerializableExtra("importerType"); - startImport(importerType, (File) getIntent().getSerializableExtra("file")); + DatabaseImporter.Definition importerDef = (DatabaseImporter.Definition) getIntent().getSerializableExtra("importerDef"); + startImport(importerDef, (File) getIntent().getSerializableExtra("file")); } - private void startImport(@NonNull Class importerType, @Nullable File file) { + private void startImport(DatabaseImporter.Definition importerDef, @Nullable File file) { + DatabaseImporter importer = DatabaseImporter.create(this, importerDef.getType()); if (file == null) { - startImportApp(importerType); + if (importer.isInstalledAppVersionSupported()) { + startImportApp(importer); + } else { + Dialogs.showSecureDialog(new AlertDialog.Builder(this) + .setTitle(R.string.warning) + .setMessage(getString(R.string.app_version_error, importerDef.getName())) + .setCancelable(false) + .setPositiveButton(R.string.yes, (dialog1, which) -> { + startImportApp(importer); + }) + .setNegativeButton(R.string.no, (dialog1, which) -> { + finish(); + }) + .create()); + } } else { - startImportFile(importerType, file); + startImportFile(importer, file); } } - private void startImportFile(@NonNull Class importerType, @NonNull File file) { + private void startImportFile(@NonNull DatabaseImporter importer, @NonNull File file) { try (InputStream stream = new FileInputStream(file)) { - DatabaseImporter importer = DatabaseImporter.create(this, importerType); DatabaseImporter.State state = importer.read(stream); processImporterState(state); } catch (FileNotFoundException e) { @@ -108,9 +122,7 @@ public class ImportEntriesActivity extends AegisActivity { } } - private void startImportApp(@NonNull Class importerType) { - DatabaseImporter importer = DatabaseImporter.create(this, importerType); - + private void startImportApp(@NonNull DatabaseImporter importer) { // obtain the global root shell and close it immediately after we're done // TODO: find a way to use SuFileInputStream with Shell.newInstance() try (Shell shell = Shell.getShell()) { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/ImportExportPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/ImportExportPreferencesFragment.java index 3f592bf2..49877350 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/ImportExportPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/ImportExportPreferencesFragment.java @@ -42,7 +42,7 @@ import javax.crypto.Cipher; public class ImportExportPreferencesFragment extends PreferencesFragment { // keep a reference to the type of database converter that was selected - private Class _importerType; + private DatabaseImporter.Definition _importerDef; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @@ -50,13 +50,13 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { addPreferencesFromResource(R.xml.preferences_import_export); if (savedInstanceState != null) { - _importerType = (Class) savedInstanceState.getSerializable("importerType"); + _importerDef = (DatabaseImporter.Definition) savedInstanceState.getSerializable("importerDef"); } Preference importPreference = requirePreference("pref_import"); importPreference.setOnPreferenceClickListener(preference -> { Dialogs.showImportersDialog(requireContext(), false, definition -> { - _importerType = definition.getType(); + _importerDef = definition; Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); @@ -68,7 +68,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { Preference importAppPreference = requirePreference("pref_import_app"); importAppPreference.setOnPreferenceClickListener(preference -> { Dialogs.showImportersDialog(requireContext(), true, definition -> { - startImportEntriesActivity(definition.getType(), null); + startImportEntriesActivity(definition, null); }); return true; }); @@ -83,7 +83,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - outState.putSerializable("importerType", _importerType); + outState.putSerializable("importerDef", _importerDef); } @Override @@ -115,7 +115,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { ImportFileTask.Params params = new ImportFileTask.Params(uri, "import", null); ImportFileTask task = new ImportFileTask(requireContext(), result -> { if (result.getException() == null) { - startImportEntriesActivity(_importerType, result.getFile()); + startImportEntriesActivity(_importerDef, result.getFile()); } else { Dialogs.showErrorDialog(requireContext(), R.string.reading_file_error, result.getException()); } @@ -123,9 +123,9 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { task.execute(getLifecycle(), params); } - private void startImportEntriesActivity(Class importerType, File file) { + private void startImportEntriesActivity(DatabaseImporter.Definition importerDef, File file) { Intent intent = new Intent(requireActivity(), ImportEntriesActivity.class); - intent.putExtra("importerType", importerType); + intent.putExtra("importerDef", importerDef); intent.putExtra("file", file); startActivityForResult(intent, CODE_IMPORT); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 946254cb..b14ffee7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ PIN (4–16 digits) Suggested Usage count + Warning App Entries @@ -205,6 +206,7 @@ Error: File not found An error occurred while trying to read the file Error: App is not installed + The version of %s that\'s installed is not supported. Attempting to import may result in an error. Would you like to continue anyway? Error: unable to obtain root access Imported %d entry @@ -381,14 +383,14 @@ Supply a copy of /data/data/com.duosecurity.duomobile/files/duokit/accounts.json, located in the internal storage directory of DUO. Supply a copy of /data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml, located in the internal storage directory of FreeOTP. Supply a FreeOTP+ export file. - Supply a copy of /data/data/com.google.android.apps.authenticator2/databases/databases, located in the internal storage directory of Google Authenticator. + Only database files from Google Authenticator v5.10 and prior are supported.\n\nSupply a copy of /data/data/com.google.android.apps.authenticator2/databases/databases, located in the internal storage directory of Google Authenticator. Supply a copy of /data/data/com.azure.authenticator/databases/PhoneFactor, located in the internal storage directory of Microsoft Authenticator. Supply a plain text file with a Google Authenticator URI on each line. Supply a copy of /data/data/com.valvesoftware.android.steam.community/files/Steamguard-*.json, located in the internal storage directory of Steam. Supply a TOTP Authenticator export file. Supply a WinAuth export file. - "Encrypted entry was skipped: %s" + Encrypted entry was skipped: %s Import entries directly from %s. This requires the app to be installed on this device and for root access to be granted to Aegis. Groups