From 8287232866f417830acd6f6d5389cfe3792cf7be Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Tue, 2 Jan 2018 13:59:24 +0100 Subject: [PATCH] display the last opened entry as an additional dataset, helps to fill paypal app and helps with partitioned data --- src/Kp2aBusinessLogic/database/Database.cs | 1 + .../database/PwEntryOutput.cs | 5 ++ src/keepass2android/ShareUrlResults.cs | 9 ++-- src/keepass2android/app/AppTask.cs | 26 ++++++++-- .../AutofillBase/AutofillServiceBase.cs | 50 ++++++++++++++----- .../ChooseForAutofillActivityBase.cs | 13 ++--- .../Kp2aAutofill/ChooseForAutofillActivity.cs | 35 +++++++------ .../Kp2aAutofill/Kp2aAutofillService.cs | 10 ++++ .../services/Kp2aAutofillIntentBuilder.cs | 3 +- 9 files changed, 110 insertions(+), 42 deletions(-) diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs index c795e082..c236a806 100644 --- a/src/Kp2aBusinessLogic/database/Database.cs +++ b/src/Kp2aBusinessLogic/database/Database.cs @@ -289,6 +289,7 @@ namespace keepass2android CanWrite = true; _reloadRequested = false; OtpAuxFileIoc = null; + LastOpenedEntry = null; } public void MarkAllGroupsAsDirty() { diff --git a/src/Kp2aBusinessLogic/database/PwEntryOutput.cs b/src/Kp2aBusinessLogic/database/PwEntryOutput.cs index 8ab57d0b..26d0ea18 100644 --- a/src/Kp2aBusinessLogic/database/PwEntryOutput.cs +++ b/src/Kp2aBusinessLogic/database/PwEntryOutput.cs @@ -56,5 +56,10 @@ namespace keepass2android { get { return _entry; } } + + /// + /// if the entry was selected by searching for a URL, the query URL is returned here. + /// + public string SearchUrl { get; set; } } } \ No newline at end of file diff --git a/src/keepass2android/ShareUrlResults.cs b/src/keepass2android/ShareUrlResults.cs index a5083b18..c1703a59 100644 --- a/src/keepass2android/ShareUrlResults.cs +++ b/src/keepass2android/ShareUrlResults.cs @@ -80,8 +80,9 @@ namespace keepass2android _db = App.Kp2a.GetDb(); if (App.Kp2a.DatabaseIsUnlocked) { - String searchUrl = ((SearchUrlTask)AppTask).UrlToSearchFor; - Query(searchUrl); + var searchUrlTask = ((SearchUrlTask)AppTask); + String searchUrl = searchUrlTask.UrlToSearchFor; + Query(searchUrl, searchUrlTask.AutoReturnFromQuery); } // else: LockCloseListActivity.OnResume will trigger a broadcast (LockDatabase) which will cause the activity to be finished. @@ -93,7 +94,7 @@ namespace keepass2android AppTask.ToBundle(outState); } - private void Query(String url) + private void Query(string url, bool autoReturnFromQuery) { try { @@ -125,7 +126,7 @@ namespace keepass2android } //if there is exactly one match: open the entry - if (Group.Entries.Count() == 1) + if ((Group.Entries.Count() == 1) && autoReturnFromQuery) { LaunchActivityForEntry(Group.Entries.Single(),0); return; diff --git a/src/keepass2android/app/AppTask.cs b/src/keepass2android/app/AppTask.cs index d868344c..d407b50e 100644 --- a/src/keepass2android/app/AppTask.cs +++ b/src/keepass2android/app/AppTask.cs @@ -361,6 +361,7 @@ namespace keepass2android { base.Setup(b); UrlToSearchFor = b.GetString(UrlToSearchKey); + AutoReturnFromQuery = b.GetBoolean(AutoReturnFromQueryKey, true); } public override IEnumerable Extras { @@ -369,9 +370,15 @@ namespace keepass2android foreach (IExtra e in base.Extras) yield return e; yield return new StringExtra { Key=UrlToSearchKey, Value = UrlToSearchFor }; - } + yield return new BoolExtra { Key = AutoReturnFromQueryKey, Value = AutoReturnFromQuery }; + } } - public override void AfterUnlockDatabase(PasswordActivity act) + + public const String AutoReturnFromQueryKey = "AutoReturnFromQuery"; + + public bool AutoReturnFromQuery { get; set; } + + public override void AfterUnlockDatabase(PasswordActivity act) { if (String.IsNullOrEmpty(UrlToSearchFor)) { @@ -403,6 +410,12 @@ namespace keepass2android base.PopulatePasswordAccessServiceIntent(intent); intent.PutExtra(UrlToSearchKey, UrlToSearchFor); } + + public override void CompleteOnCreateEntryActivity(EntryActivity activity) + { + App.Kp2a.GetDb().LastOpenedEntry.SearchUrl = UrlToSearchFor; + base.CompleteOnCreateEntryActivity(activity); + } } @@ -516,15 +529,18 @@ namespace keepass2android public override void CompleteOnCreateEntryActivity(EntryActivity activity) { - //if the database is readonly (or no URL exists), don't offer to modify the URL - if ((App.Kp2a.GetDb().CanWrite == false) || (String.IsNullOrEmpty(UrlToSearchFor))) + App.Kp2a.GetDb().LastOpenedEntry.SearchUrl = UrlToSearchFor; + + //if the database is readonly (or no URL exists), don't offer to modify the URL + if ((App.Kp2a.GetDb().CanWrite == false) || (String.IsNullOrEmpty(UrlToSearchFor))) { base.CompleteOnCreateEntryActivity(activity); return; } + - AskAddUrlThenCompleteCreate(activity, UrlToSearchFor); + AskAddUrlThenCompleteCreate(activity, UrlToSearchFor); } diff --git a/src/keepass2android/services/AutofillBase/AutofillServiceBase.cs b/src/keepass2android/services/AutofillBase/AutofillServiceBase.cs index f363c96f..94cf6b1b 100644 --- a/src/keepass2android/services/AutofillBase/AutofillServiceBase.cs +++ b/src/keepass2android/services/AutofillBase/AutofillServiceBase.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Android.Content; using Android.OS; using Android.Runtime; @@ -6,12 +7,13 @@ using Android.Service.Autofill; using Android.Util; using Android.Views.Autofill; using Android.Widget; +using keepass2android.services.AutofillBase.model; namespace keepass2android.services.AutofillBase { public interface IAutofillIntentBuilder { - IntentSender GetAuthIntentSenderForResponse(Context context, string query, bool isManualRequest); + IntentSender GetAuthIntentSenderForResponse(Context context, string query, bool isManualRequest, bool autoReturnFromQuery); Intent GetRestartAppIntent(Context context); int AppIconResource { get; } @@ -67,19 +69,13 @@ namespace keepass2android.services.AutofillBase if (responseAuth && autofillIds.Length != 0 && CanAutofill(query)) { var responseBuilder = new FillResponse.Builder(); - - var sender = IntentBuilder.GetAuthIntentSenderForResponse(this, query, isManual); - RemoteViews presentation = AutofillHelper.NewRemoteViews(PackageName, GetString(Resource.String.autofill_sign_in_prompt), AppNames.LauncherIcon); - var datasetBuilder = new Dataset.Builder(presentation); - datasetBuilder.SetAuthentication(sender); - //need to add placeholders so we can directly fill after ChooseActivity - foreach (var autofillId in autofillIds) - { - datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER")); - } + var entryDataset = AddEntryDataset(query, parser); + bool hasEntryDataset = entryDataset != null; + if (entryDataset != null) + responseBuilder.AddDataset(entryDataset); - responseBuilder.AddDataset(datasetBuilder.Build()); + AddQueryDataset(query, isManual, autofillIds, responseBuilder, !hasEntryDataset); callback.OnSuccess(responseBuilder.Build()); } @@ -91,6 +87,36 @@ namespace keepass2android.services.AutofillBase } } + private Dataset AddEntryDataset(string query, StructureParser parser) + { + var filledAutofillFieldCollection = GetSuggestedEntry(query); + if (filledAutofillFieldCollection == null) + return null; + int partitionIndex = AutofillHintsHelper.GetPartitionIndex(parser.AutofillFields.FocusedAutofillCanonicalHints.FirstOrDefault()); + FilledAutofillFieldCollection partitionData = AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, partitionIndex); + + return AutofillHelper.NewDataset(this, parser.AutofillFields, partitionData, IntentBuilder); + } + + protected abstract FilledAutofillFieldCollection GetSuggestedEntry(string query); + + private void AddQueryDataset(string query, bool isManual, AutofillId[] autofillIds, FillResponse.Builder responseBuilder, bool autoReturnFromQuery) + { + var sender = IntentBuilder.GetAuthIntentSenderForResponse(this, query, isManual, autoReturnFromQuery); + RemoteViews presentation = AutofillHelper.NewRemoteViews(PackageName, + GetString(Resource.String.autofill_sign_in_prompt), AppNames.LauncherIcon); + + var datasetBuilder = new Dataset.Builder(presentation); + datasetBuilder.SetAuthentication(sender); + //need to add placeholders so we can directly fill after ChooseActivity + foreach (var autofillId in autofillIds) + { + datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER")); + } + + responseBuilder.AddDataset(datasetBuilder.Build()); + } + private bool CanAutofill(string query) { return !(query == "androidapp://android" || query == "androidapp://"+this.PackageName); diff --git a/src/keepass2android/services/AutofillBase/ChooseForAutofillActivityBase.cs b/src/keepass2android/services/AutofillBase/ChooseForAutofillActivityBase.cs index 18c2c733..f088cc26 100644 --- a/src/keepass2android/services/AutofillBase/ChooseForAutofillActivityBase.cs +++ b/src/keepass2android/services/AutofillBase/ChooseForAutofillActivityBase.cs @@ -23,6 +23,7 @@ namespace keepass2android.services.AutofillBase public static string ExtraQueryString => "EXTRA_QUERY_STRING"; public static string ExtraIsManualRequest => "EXTRA_IS_MANUAL_REQUEST"; + public static string ExtraAutoReturnFromQuery => "EXTRA_AUTO_RETURN_FROM_QUERY"; public int RequestCodeQuery => 6245; @@ -46,11 +47,11 @@ namespace keepass2android.services.AutofillBase return; } - var i = GetQueryIntent(requestedUrl); + var i = GetQueryIntent(requestedUrl, Intent.GetBooleanExtra(ExtraAutoReturnFromQuery, true)); StartActivityForResult(i, RequestCodeQuery); } - protected abstract Intent GetQueryIntent(string requestedUrl); + protected abstract Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery); protected void RestartApp() { @@ -59,7 +60,7 @@ namespace keepass2android.services.AutofillBase Finish(); } - /* + public override void Finish() { if (ReplyIntent != null) @@ -71,7 +72,7 @@ namespace keepass2android.services.AutofillBase SetResult(Result.Canceled); } base.Finish(); - }*/ + } void OnFailure() { @@ -87,8 +88,7 @@ namespace keepass2android.services.AutofillBase parser.ParseForFill(isManual); AutofillFieldMetadataCollection autofillFields = parser.AutofillFields; int partitionIndex = AutofillHintsHelper.GetPartitionIndex(autofillFields.FocusedAutofillCanonicalHints.FirstOrDefault()); - FilledAutofillFieldCollection partitionData = - AutofillHintsHelper.FilterForPartition(clientFormDataMap, partitionIndex); + FilledAutofillFieldCollection partitionData = AutofillHintsHelper.FilterForPartition(clientFormDataMap, partitionIndex); ReplyIntent = new Intent(); SetDatasetIntent(AutofillHelper.NewDataset(this, autofillFields, partitionData, IntentBuilder)); } @@ -121,6 +121,7 @@ namespace keepass2android.services.AutofillBase public abstract IAutofillIntentBuilder IntentBuilder { get; } + protected void SetResponseIntent(FillResponse fillResponse) { ReplyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, fillResponse); diff --git a/src/keepass2android/services/Kp2aAutofill/ChooseForAutofillActivity.cs b/src/keepass2android/services/Kp2aAutofill/ChooseForAutofillActivity.cs index c51dad86..0dcd33f3 100644 --- a/src/keepass2android/services/Kp2aAutofill/ChooseForAutofillActivity.cs +++ b/src/keepass2android/services/Kp2aAutofill/ChooseForAutofillActivity.cs @@ -24,13 +24,13 @@ namespace keepass2android.services.Kp2aAutofill Permission = "keepass2android." + AppNames.PackagePart + ".permission.Kp2aChooseAutofill")] public class ChooseForAutofillActivity : ChooseForAutofillActivityBase { - protected override Intent GetQueryIntent(string requestedUrl) + protected override Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery) { //launch FileSelectActivity (which is root of the stack (exception: we're even below!)) with the appropriate task. //will return the results later Intent i = new Intent(this, typeof(FileSelectActivity)); //don't show user notifications when an entry is opened. - var task = new SearchUrlTask() { UrlToSearchFor = requestedUrl, ShowUserNotifications = false }; + var task = new SearchUrlTask() { UrlToSearchFor = requestedUrl, ShowUserNotifications = false, AutoReturnFromQuery = autoReturnFromQuery }; task.ToIntent(i); return i; } @@ -41,29 +41,37 @@ namespace keepass2android.services.Kp2aAutofill { if (!App.Kp2a.GetDb().Loaded || (App.Kp2a.QuickLocked)) return null; + var entryOutput = App.Kp2a.GetDb().LastOpenedEntry; + return GetFilledAutofillFieldCollectionFromEntry(entryOutput, this); + } + + public static FilledAutofillFieldCollection GetFilledAutofillFieldCollectionFromEntry(PwEntryOutput pwEntryOutput, Context context) + { + if (pwEntryOutput == null) + return null; FilledAutofillFieldCollection fieldCollection = new FilledAutofillFieldCollection(); + var pwEntry = pwEntryOutput.Entry; - var pwEntry = App.Kp2a.GetDb().LastOpenedEntry.Entry; foreach (string key in pwEntry.Strings.GetKeys()) { FilledAutofillField field = new FilledAutofillField { - AutofillHints = new[] { GetCanonicalHintFromKp2aField(pwEntry, key) }, + AutofillHints = new[] {GetCanonicalHintFromKp2aField(pwEntry, key)}, TextValue = pwEntry.Strings.ReadSafe(key), Protected = pwEntry.Strings.Get(key).IsProtected }; fieldCollection.Add(field); } - if (IsCreditCard(pwEntry) && pwEntry.Expires) + if (IsCreditCard(pwEntry, context) && pwEntry.Expires) { DateTime expTime = pwEntry.ExpiryTime; FilledAutofillField field = new FilledAutofillField { - AutofillHints = new[] { View.AutofillHintCreditCardExpirationDate }, - DateValue = (long)(1000*TimeUtil.SerializeUnix(expTime)), + AutofillHints = new[] {View.AutofillHintCreditCardExpirationDate}, + DateValue = (long) (1000 * TimeUtil.SerializeUnix(expTime)), Protected = false }; fieldCollection.Add(field); @@ -71,7 +79,7 @@ namespace keepass2android.services.Kp2aAutofill field = new FilledAutofillField { - AutofillHints = new[] { View.AutofillHintCreditCardExpirationDay }, + AutofillHints = new[] {View.AutofillHintCreditCardExpirationDay}, TextValue = expTime.Day.ToString(), Protected = false }; @@ -80,7 +88,7 @@ namespace keepass2android.services.Kp2aAutofill field = new FilledAutofillField { - AutofillHints = new[] { View.AutofillHintCreditCardExpirationMonth }, + AutofillHints = new[] {View.AutofillHintCreditCardExpirationMonth}, TextValue = expTime.Month.ToString(), Protected = false }; @@ -89,13 +97,12 @@ namespace keepass2android.services.Kp2aAutofill field = new FilledAutofillField { - AutofillHints = new[] { View.AutofillHintCreditCardExpirationYear }, + AutofillHints = new[] {View.AutofillHintCreditCardExpirationYear}, TextValue = expTime.Year.ToString(), Protected = false }; fieldCollection.Add(field); } - fieldCollection.DatasetName = pwEntry.Strings.ReadSafe(PwDefs.TitleField); @@ -103,11 +110,11 @@ namespace keepass2android.services.Kp2aAutofill return fieldCollection; } - private bool IsCreditCard(PwEntry pwEntry) + private static bool IsCreditCard(PwEntry pwEntry, Context context) { return pwEntry.Strings.Exists("cc-number") || pwEntry.Strings.Exists("cc-csc") - || pwEntry.Strings.Exists(GetString(Resource.String.TemplateField_CreditCard_CVV)); + || pwEntry.Strings.Exists(context.GetString(Resource.String.TemplateField_CreditCard_CVV)); } private static readonly Dictionary keyToHint = BuildKeyToHint(); @@ -133,7 +140,7 @@ namespace keepass2android.services.Kp2aAutofill return result; } - private string GetCanonicalHintFromKp2aField(PwEntry pwEntry, string key) + private static string GetCanonicalHintFromKp2aField(PwEntry pwEntry, string key) { if (!keyToHint.TryGetValue(key, out string result)) result = key; diff --git a/src/keepass2android/services/Kp2aAutofill/Kp2aAutofillService.cs b/src/keepass2android/services/Kp2aAutofill/Kp2aAutofillService.cs index 90220048..7dc24713 100644 --- a/src/keepass2android/services/Kp2aAutofill/Kp2aAutofillService.cs +++ b/src/keepass2android/services/Kp2aAutofill/Kp2aAutofillService.cs @@ -4,6 +4,8 @@ using Android.App; using Android.Content; using Android.Runtime; using keepass2android.services.AutofillBase; +using keepass2android.services.AutofillBase.model; +using keepass2android.services.Kp2aAutofill; using AutofillServiceBase = keepass2android.services.AutofillBase.AutofillServiceBase; namespace keepass2android.services @@ -24,6 +26,14 @@ namespace keepass2android.services { } + protected override FilledAutofillFieldCollection GetSuggestedEntry(string query) + { + if (App.Kp2a.GetDb()?.LastOpenedEntry?.SearchUrl == query) + return ChooseForAutofillActivity.GetFilledAutofillFieldCollectionFromEntry( + App.Kp2a.GetDb()?.LastOpenedEntry, this); + return null; + } + public override IAutofillIntentBuilder IntentBuilder => new Kp2aAutofillIntentBuilder(); } } \ No newline at end of file diff --git a/src/keepass2android/services/Kp2aAutofillIntentBuilder.cs b/src/keepass2android/services/Kp2aAutofillIntentBuilder.cs index 26578e22..12830a83 100644 --- a/src/keepass2android/services/Kp2aAutofillIntentBuilder.cs +++ b/src/keepass2android/services/Kp2aAutofillIntentBuilder.cs @@ -15,11 +15,12 @@ namespace keepass2android.services class Kp2aAutofillIntentBuilder: IAutofillIntentBuilder { - public IntentSender GetAuthIntentSenderForResponse(Context context, string query, bool isManualRequest) + public IntentSender GetAuthIntentSenderForResponse(Context context, string query, bool isManualRequest, bool autoReturnFromQuery) { Intent intent = new Intent(context, typeof(ChooseForAutofillActivity)); intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryString, query); intent.PutExtra(ChooseForAutofillActivityBase.ExtraIsManualRequest, isManualRequest); + intent.PutExtra(ChooseForAutofillActivityBase.ExtraAutoReturnFromQuery, autoReturnFromQuery); return PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.CancelCurrent).IntentSender; }