display the last opened entry as an additional dataset, helps to fill paypal app and helps with partitioned data

This commit is contained in:
Philipp Crocoll
2018-01-02 13:59:24 +01:00
parent 11330b608b
commit 8287232866
9 changed files with 110 additions and 42 deletions

View File

@@ -289,6 +289,7 @@ namespace keepass2android
CanWrite = true; CanWrite = true;
_reloadRequested = false; _reloadRequested = false;
OtpAuxFileIoc = null; OtpAuxFileIoc = null;
LastOpenedEntry = null;
} }
public void MarkAllGroupsAsDirty() { public void MarkAllGroupsAsDirty() {

View File

@@ -56,5 +56,10 @@ namespace keepass2android
{ {
get { return _entry; } get { return _entry; }
} }
/// <summary>
/// if the entry was selected by searching for a URL, the query URL is returned here.
/// </summary>
public string SearchUrl { get; set; }
} }
} }

View File

@@ -80,8 +80,9 @@ namespace keepass2android
_db = App.Kp2a.GetDb(); _db = App.Kp2a.GetDb();
if (App.Kp2a.DatabaseIsUnlocked) if (App.Kp2a.DatabaseIsUnlocked)
{ {
String searchUrl = ((SearchUrlTask)AppTask).UrlToSearchFor; var searchUrlTask = ((SearchUrlTask)AppTask);
Query(searchUrl); String searchUrl = searchUrlTask.UrlToSearchFor;
Query(searchUrl, searchUrlTask.AutoReturnFromQuery);
} }
// else: LockCloseListActivity.OnResume will trigger a broadcast (LockDatabase) which will cause the activity to be finished. // 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); AppTask.ToBundle(outState);
} }
private void Query(String url) private void Query(string url, bool autoReturnFromQuery)
{ {
try try
{ {
@@ -125,7 +126,7 @@ namespace keepass2android
} }
//if there is exactly one match: open the entry //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); LaunchActivityForEntry(Group.Entries.Single(),0);
return; return;

View File

@@ -361,6 +361,7 @@ namespace keepass2android
{ {
base.Setup(b); base.Setup(b);
UrlToSearchFor = b.GetString(UrlToSearchKey); UrlToSearchFor = b.GetString(UrlToSearchKey);
AutoReturnFromQuery = b.GetBoolean(AutoReturnFromQueryKey, true);
} }
public override IEnumerable<IExtra> Extras public override IEnumerable<IExtra> Extras
{ {
@@ -369,9 +370,15 @@ namespace keepass2android
foreach (IExtra e in base.Extras) foreach (IExtra e in base.Extras)
yield return e; yield return e;
yield return new StringExtra { Key=UrlToSearchKey, Value = UrlToSearchFor }; 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)) if (String.IsNullOrEmpty(UrlToSearchFor))
{ {
@@ -403,6 +410,12 @@ namespace keepass2android
base.PopulatePasswordAccessServiceIntent(intent); base.PopulatePasswordAccessServiceIntent(intent);
intent.PutExtra(UrlToSearchKey, UrlToSearchFor); 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) public override void CompleteOnCreateEntryActivity(EntryActivity activity)
{ {
//if the database is readonly (or no URL exists), don't offer to modify the URL App.Kp2a.GetDb().LastOpenedEntry.SearchUrl = UrlToSearchFor;
if ((App.Kp2a.GetDb().CanWrite == false) || (String.IsNullOrEmpty(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); base.CompleteOnCreateEntryActivity(activity);
return; return;
} }
AskAddUrlThenCompleteCreate(activity, UrlToSearchFor);
AskAddUrlThenCompleteCreate(activity, UrlToSearchFor);
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Android.Runtime; using Android.Runtime;
@@ -6,12 +7,13 @@ using Android.Service.Autofill;
using Android.Util; using Android.Util;
using Android.Views.Autofill; using Android.Views.Autofill;
using Android.Widget; using Android.Widget;
using keepass2android.services.AutofillBase.model;
namespace keepass2android.services.AutofillBase namespace keepass2android.services.AutofillBase
{ {
public interface IAutofillIntentBuilder 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); Intent GetRestartAppIntent(Context context);
int AppIconResource { get; } int AppIconResource { get; }
@@ -68,18 +70,12 @@ namespace keepass2android.services.AutofillBase
{ {
var responseBuilder = new FillResponse.Builder(); var responseBuilder = new FillResponse.Builder();
var sender = IntentBuilder.GetAuthIntentSenderForResponse(this, query, isManual); var entryDataset = AddEntryDataset(query, parser);
RemoteViews presentation = AutofillHelper.NewRemoteViews(PackageName, GetString(Resource.String.autofill_sign_in_prompt), AppNames.LauncherIcon); bool hasEntryDataset = entryDataset != null;
if (entryDataset != null)
responseBuilder.AddDataset(entryDataset);
var datasetBuilder = new Dataset.Builder(presentation); AddQueryDataset(query, isManual, autofillIds, responseBuilder, !hasEntryDataset);
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());
callback.OnSuccess(responseBuilder.Build()); 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) private bool CanAutofill(string query)
{ {
return !(query == "androidapp://android" || query == "androidapp://"+this.PackageName); return !(query == "androidapp://android" || query == "androidapp://"+this.PackageName);

View File

@@ -23,6 +23,7 @@ namespace keepass2android.services.AutofillBase
public static string ExtraQueryString => "EXTRA_QUERY_STRING"; public static string ExtraQueryString => "EXTRA_QUERY_STRING";
public static string ExtraIsManualRequest => "EXTRA_IS_MANUAL_REQUEST"; public static string ExtraIsManualRequest => "EXTRA_IS_MANUAL_REQUEST";
public static string ExtraAutoReturnFromQuery => "EXTRA_AUTO_RETURN_FROM_QUERY";
public int RequestCodeQuery => 6245; public int RequestCodeQuery => 6245;
@@ -46,11 +47,11 @@ namespace keepass2android.services.AutofillBase
return; return;
} }
var i = GetQueryIntent(requestedUrl); var i = GetQueryIntent(requestedUrl, Intent.GetBooleanExtra(ExtraAutoReturnFromQuery, true));
StartActivityForResult(i, RequestCodeQuery); StartActivityForResult(i, RequestCodeQuery);
} }
protected abstract Intent GetQueryIntent(string requestedUrl); protected abstract Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery);
protected void RestartApp() protected void RestartApp()
{ {
@@ -59,7 +60,7 @@ namespace keepass2android.services.AutofillBase
Finish(); Finish();
} }
/*
public override void Finish() public override void Finish()
{ {
if (ReplyIntent != null) if (ReplyIntent != null)
@@ -71,7 +72,7 @@ namespace keepass2android.services.AutofillBase
SetResult(Result.Canceled); SetResult(Result.Canceled);
} }
base.Finish(); base.Finish();
}*/ }
void OnFailure() void OnFailure()
{ {
@@ -87,8 +88,7 @@ namespace keepass2android.services.AutofillBase
parser.ParseForFill(isManual); parser.ParseForFill(isManual);
AutofillFieldMetadataCollection autofillFields = parser.AutofillFields; AutofillFieldMetadataCollection autofillFields = parser.AutofillFields;
int partitionIndex = AutofillHintsHelper.GetPartitionIndex(autofillFields.FocusedAutofillCanonicalHints.FirstOrDefault()); int partitionIndex = AutofillHintsHelper.GetPartitionIndex(autofillFields.FocusedAutofillCanonicalHints.FirstOrDefault());
FilledAutofillFieldCollection partitionData = FilledAutofillFieldCollection partitionData = AutofillHintsHelper.FilterForPartition(clientFormDataMap, partitionIndex);
AutofillHintsHelper.FilterForPartition(clientFormDataMap, partitionIndex);
ReplyIntent = new Intent(); ReplyIntent = new Intent();
SetDatasetIntent(AutofillHelper.NewDataset(this, autofillFields, partitionData, IntentBuilder)); SetDatasetIntent(AutofillHelper.NewDataset(this, autofillFields, partitionData, IntentBuilder));
} }
@@ -121,6 +121,7 @@ namespace keepass2android.services.AutofillBase
public abstract IAutofillIntentBuilder IntentBuilder { get; } public abstract IAutofillIntentBuilder IntentBuilder { get; }
protected void SetResponseIntent(FillResponse fillResponse) protected void SetResponseIntent(FillResponse fillResponse)
{ {
ReplyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, fillResponse); ReplyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, fillResponse);

View File

@@ -24,13 +24,13 @@ namespace keepass2android.services.Kp2aAutofill
Permission = "keepass2android." + AppNames.PackagePart + ".permission.Kp2aChooseAutofill")] Permission = "keepass2android." + AppNames.PackagePart + ".permission.Kp2aChooseAutofill")]
public class ChooseForAutofillActivity : ChooseForAutofillActivityBase 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. //launch FileSelectActivity (which is root of the stack (exception: we're even below!)) with the appropriate task.
//will return the results later //will return the results later
Intent i = new Intent(this, typeof(FileSelectActivity)); Intent i = new Intent(this, typeof(FileSelectActivity));
//don't show user notifications when an entry is opened. //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); task.ToIntent(i);
return i; return i;
} }
@@ -41,29 +41,37 @@ namespace keepass2android.services.Kp2aAutofill
{ {
if (!App.Kp2a.GetDb().Loaded || (App.Kp2a.QuickLocked)) if (!App.Kp2a.GetDb().Loaded || (App.Kp2a.QuickLocked))
return null; 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(); FilledAutofillFieldCollection fieldCollection = new FilledAutofillFieldCollection();
var pwEntry = pwEntryOutput.Entry;
var pwEntry = App.Kp2a.GetDb().LastOpenedEntry.Entry;
foreach (string key in pwEntry.Strings.GetKeys()) foreach (string key in pwEntry.Strings.GetKeys())
{ {
FilledAutofillField field = FilledAutofillField field =
new FilledAutofillField new FilledAutofillField
{ {
AutofillHints = new[] { GetCanonicalHintFromKp2aField(pwEntry, key) }, AutofillHints = new[] {GetCanonicalHintFromKp2aField(pwEntry, key)},
TextValue = pwEntry.Strings.ReadSafe(key), TextValue = pwEntry.Strings.ReadSafe(key),
Protected = pwEntry.Strings.Get(key).IsProtected Protected = pwEntry.Strings.Get(key).IsProtected
}; };
fieldCollection.Add(field); fieldCollection.Add(field);
} }
if (IsCreditCard(pwEntry) && pwEntry.Expires) if (IsCreditCard(pwEntry, context) && pwEntry.Expires)
{ {
DateTime expTime = pwEntry.ExpiryTime; DateTime expTime = pwEntry.ExpiryTime;
FilledAutofillField field = FilledAutofillField field =
new FilledAutofillField new FilledAutofillField
{ {
AutofillHints = new[] { View.AutofillHintCreditCardExpirationDate }, AutofillHints = new[] {View.AutofillHintCreditCardExpirationDate},
DateValue = (long)(1000*TimeUtil.SerializeUnix(expTime)), DateValue = (long) (1000 * TimeUtil.SerializeUnix(expTime)),
Protected = false Protected = false
}; };
fieldCollection.Add(field); fieldCollection.Add(field);
@@ -71,7 +79,7 @@ namespace keepass2android.services.Kp2aAutofill
field = field =
new FilledAutofillField new FilledAutofillField
{ {
AutofillHints = new[] { View.AutofillHintCreditCardExpirationDay }, AutofillHints = new[] {View.AutofillHintCreditCardExpirationDay},
TextValue = expTime.Day.ToString(), TextValue = expTime.Day.ToString(),
Protected = false Protected = false
}; };
@@ -80,7 +88,7 @@ namespace keepass2android.services.Kp2aAutofill
field = field =
new FilledAutofillField new FilledAutofillField
{ {
AutofillHints = new[] { View.AutofillHintCreditCardExpirationMonth }, AutofillHints = new[] {View.AutofillHintCreditCardExpirationMonth},
TextValue = expTime.Month.ToString(), TextValue = expTime.Month.ToString(),
Protected = false Protected = false
}; };
@@ -89,7 +97,7 @@ namespace keepass2android.services.Kp2aAutofill
field = field =
new FilledAutofillField new FilledAutofillField
{ {
AutofillHints = new[] { View.AutofillHintCreditCardExpirationYear }, AutofillHints = new[] {View.AutofillHintCreditCardExpirationYear},
TextValue = expTime.Year.ToString(), TextValue = expTime.Year.ToString(),
Protected = false Protected = false
}; };
@@ -97,17 +105,16 @@ namespace keepass2android.services.Kp2aAutofill
} }
fieldCollection.DatasetName = pwEntry.Strings.ReadSafe(PwDefs.TitleField); fieldCollection.DatasetName = pwEntry.Strings.ReadSafe(PwDefs.TitleField);
return fieldCollection; return fieldCollection;
} }
private bool IsCreditCard(PwEntry pwEntry) private static bool IsCreditCard(PwEntry pwEntry, Context context)
{ {
return pwEntry.Strings.Exists("cc-number") return pwEntry.Strings.Exists("cc-number")
|| pwEntry.Strings.Exists("cc-csc") || 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<string, string> keyToHint = BuildKeyToHint(); private static readonly Dictionary<string, string> keyToHint = BuildKeyToHint();
@@ -133,7 +140,7 @@ namespace keepass2android.services.Kp2aAutofill
return result; return result;
} }
private string GetCanonicalHintFromKp2aField(PwEntry pwEntry, string key) private static string GetCanonicalHintFromKp2aField(PwEntry pwEntry, string key)
{ {
if (!keyToHint.TryGetValue(key, out string result)) if (!keyToHint.TryGetValue(key, out string result))
result = key; result = key;

View File

@@ -4,6 +4,8 @@ using Android.App;
using Android.Content; using Android.Content;
using Android.Runtime; using Android.Runtime;
using keepass2android.services.AutofillBase; using keepass2android.services.AutofillBase;
using keepass2android.services.AutofillBase.model;
using keepass2android.services.Kp2aAutofill;
using AutofillServiceBase = keepass2android.services.AutofillBase.AutofillServiceBase; using AutofillServiceBase = keepass2android.services.AutofillBase.AutofillServiceBase;
namespace keepass2android.services 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(); public override IAutofillIntentBuilder IntentBuilder => new Kp2aAutofillIntentBuilder();
} }
} }

View File

@@ -15,11 +15,12 @@ namespace keepass2android.services
class Kp2aAutofillIntentBuilder: IAutofillIntentBuilder 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 intent = new Intent(context, typeof(ChooseForAutofillActivity));
intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryString, query); intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryString, query);
intent.PutExtra(ChooseForAutofillActivityBase.ExtraIsManualRequest, isManualRequest); intent.PutExtra(ChooseForAutofillActivityBase.ExtraIsManualRequest, isManualRequest);
intent.PutExtra(ChooseForAutofillActivityBase.ExtraAutoReturnFromQuery, autoReturnFromQuery);
return PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.CancelCurrent).IntentSender; return PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.CancelCurrent).IntentSender;
} }