From 28379439de6542d747fb9f2fdfb0eb8d261bd57e Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Thu, 5 Nov 2015 22:58:42 -0500 Subject: search for webview recursively (& search files instead of passworditems) --- .../zeapo/pwdstore/autofill/AutofillService.java | 94 +++++++++++++++------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java index 2b32a809..894fb62b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -1,5 +1,6 @@ package com.zeapo.pwdstore.autofill; +import android.Manifest; import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.content.ClipData; @@ -10,11 +11,10 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; -import android.provider.Settings; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.WindowManager; @@ -38,6 +38,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.util.ArrayDeque; import java.util.ArrayList; public class AutofillService extends AccessibilityService { @@ -69,17 +70,32 @@ public class AutofillService extends AccessibilityService { // TODO change search/search results (just use first result) @Override public void onAccessibilityEvent(AccessibilityEvent event) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW) + == PackageManager.PERMISSION_DENIED) { + // may need a way to request the permission but only activities can, so by notification? + return; + } + // if returning to the source app from a successful AutofillActivity if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.getPackageName().equals(packageName) && resultData != null) { bindDecryptAndVerify(); } + // need to see if window has a WebView every time, so future events are sent? + AccessibilityNodeInfo source = event.getSource(); + if (source == null) { + return; + } + searchWebView(source); + // nothing to do if not password field focus, android version, or field is keychain app if (!event.isPassword() + || event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 || event.getPackageName().equals("org.sufficientlysecure.keychain")) { dismissDialog(event); + source.recycle(); // is this necessary??? return; } @@ -87,6 +103,7 @@ public class AutofillService extends AccessibilityService { // the current dialog must belong to this window; ignore clicks on this password field // why handle clicks at all then? some cases e.g. Paypal there is no initial focus event if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) { + source.recycle(); return; } // if it was not a click, the field was refocused or another field was focused; recreate @@ -96,20 +113,13 @@ public class AutofillService extends AccessibilityService { // ignore the ACTION_FOCUS from decryptAndVerify otherwise dialog will appear after Fill if (ignoreActionFocus) { ignoreActionFocus = false; + source.recycle(); return; } - // need to request permission before attempting to draw dialog - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && !Settings.canDrawOverlays(this)) { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + getApplicationContext().getPackageName())); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - return; - } - - info = event.getSource(); + // we are now going to attempt to fill, save AccessibilityNodeInfo for later in decryptAndVerify + // (there should be a proper way to do this, although this seems to work 90% of the time) + info = source; // save the dialog's corresponding window so we can use getWindows() in dismissDialog if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -126,7 +136,7 @@ public class AutofillService extends AccessibilityService { } final String appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString(); - getMatchingPassword(appName, info.getPackageName().toString()); + setMatchingPasswords(appName, info.getPackageName().toString()); if (items.isEmpty()) { return; } @@ -134,6 +144,24 @@ public class AutofillService extends AccessibilityService { showDialog(appName); } + private boolean searchWebView(AccessibilityNodeInfo source) { + for (int i = 0; i < source.getChildCount(); i++) { + AccessibilityNodeInfo u = source.getChild(i); + if (u == null) { + continue; + } + // this is not likely to always work + if (u.getContentDescription() != null && u.getContentDescription().equals("Web View")) { + return true; + } + if (searchWebView(u)) { + return true; + } + u.recycle(); + } + return false; + } + // dismiss the dialog if the window has changed private void dismissDialog(AccessibilityEvent event) { // the default keyboard showing/hiding is a window state changed event @@ -152,7 +180,7 @@ public class AutofillService extends AccessibilityService { } } - private void getMatchingPassword(String appName, String packageName) { + private void setMatchingPasswords(String appName, String packageName) { // if autofill_default is checked and prefs.getString DNE, 'Automatically match with password'/"first" otherwise "never" String defValue = settings.getBoolean("autofill_default", true) ? "/first" : "/never"; SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE); @@ -162,7 +190,10 @@ public class AutofillService extends AccessibilityService { if (!PasswordRepository.isInitialized()) { PasswordRepository.initialize(this); } - items = recursiveFilter(appName, null); + items = new ArrayList<>(); + for (File file : searchPasswords(PasswordRepository.getRepositoryDirectory(this), appName)) { + items.add(PasswordItem.newPassword(file.getName(), file, PasswordRepository.getRepositoryDirectory(this))); + } break; case "/never": items.clear(); @@ -178,17 +209,24 @@ public class AutofillService extends AccessibilityService { } } - private ArrayList recursiveFilter(String filter, File dir) { - ArrayList items = new ArrayList<>(); - ArrayList passwordItems = dir == null ? - PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(this)) : - PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(this)); - for (PasswordItem item : passwordItems) { - if (item.getType() == PasswordItem.TYPE_CATEGORY) { - items.addAll(recursiveFilter(filter, item.getFile())); - } - if (item.toString().toLowerCase().contains(filter.toLowerCase())) { - items.add(item); + private ArrayList searchPasswords(File path, String appName) { + ArrayList passList + = PasswordRepository.getFilesList(path); + + if (passList.size() == 0) return new ArrayList<>(); + + ArrayList items = new ArrayList<>(); + + for (File file : passList) { + if (file.isFile()) { + if (file.toString().toLowerCase().contains(appName.toLowerCase())) { + items.add(file); + } + } else { + // ignore .git directory + if (file.getName().equals(".git")) + continue; + items.addAll(searchPasswords(file, appName)); } } return items; @@ -281,6 +319,7 @@ public class AutofillService extends AccessibilityService { // if the user focused on something else, take focus back // but this will open another dialog...hack to ignore this + // & need to ensure performAction correct (i.e. what is info now?) ignoreActionFocus = info.performAction(AccessibilityNodeInfo.ACTION_FOCUS); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Bundle args = new Bundle(); @@ -302,6 +341,7 @@ public class AutofillService extends AccessibilityService { } } } + info.recycle(); } catch (UnsupportedEncodingException e) { Log.e(Constants.TAG, "UnsupportedEncodingException", e); } -- cgit v1.2.3