implemented saving of data from autofill service (#9)

This commit is contained in:
Philipp Crocoll
2018-01-02 16:28:54 +01:00
parent a929db9939
commit b9e436d56d
7 changed files with 165 additions and 60 deletions

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Android.App.Assist;
using Android.Service.Autofill;
using Android.Views;
@@ -65,47 +67,48 @@ namespace keepass2android.services.AutofillBase
return -1;
}
static readonly HashSet<string> _creditCardHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
View.AutofillHintCreditCardExpirationDate,
View.AutofillHintCreditCardExpirationDay,
View.AutofillHintCreditCardExpirationMonth,
View.AutofillHintCreditCardExpirationYear,
View.AutofillHintCreditCardNumber,
View.AutofillHintCreditCardSecurityCode
};
static readonly HashSet<string> _addressHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
View.AutofillHintPostalAddress,
View.AutofillHintPostalCode
};
void UpdateSaveTypeFromHints()
{
//TODO future add savetypes for W3cHints
SaveType = 0;
if (AutofillCanonicalHints == null)
{
return;
}
foreach (var hint in AutofillCanonicalHints)
{
switch (hint)
{
case View.AutofillHintCreditCardExpirationDate:
case View.AutofillHintCreditCardExpirationDay:
case View.AutofillHintCreditCardExpirationMonth:
case View.AutofillHintCreditCardExpirationYear:
case View.AutofillHintCreditCardNumber:
case View.AutofillHintCreditCardSecurityCode:
SaveType |= SaveDataType.CreditCard;
break;
case View.AutofillHintEmailAddress:
SaveType |= SaveDataType.EmailAddress;
break;
case View.AutofillHintPhone:
case View.AutofillHintName:
SaveType |= SaveDataType.Generic;
break;
case View.AutofillHintPassword:
SaveType |= SaveDataType.Password;
SaveType &= ~SaveDataType.EmailAddress;
SaveType &= ~SaveDataType.Username;
break;
case View.AutofillHintPostalAddress:
case View.AutofillHintPostalCode:
SaveType |= SaveDataType.Address;
break;
case View.AutofillHintUsername:
SaveType |= SaveDataType.Username;
break;
}
}
if (AutofillCanonicalHints.Any(h => _creditCardHints.Contains(h)))
{
SaveType |= SaveDataType.CreditCard;
}
if (AutofillCanonicalHints.Any(h => h.Equals(View.AutofillHintEmailAddress, StringComparison.OrdinalIgnoreCase)))
SaveType |= SaveDataType.EmailAddress;
if (AutofillCanonicalHints.Any(h => _addressHints.Contains(h)))
{
SaveType |= SaveDataType.Address;
}
if (AutofillCanonicalHints.Any(h => h.Equals(View.AutofillHintUsername, StringComparison.OrdinalIgnoreCase)))
SaveType |= SaveDataType.Username;
if (AutofillCanonicalHints.Any(h => h.Equals(View.AutofillHintPassword, StringComparison.OrdinalIgnoreCase)))
{
SaveType |= SaveDataType.Password;
SaveType &= ~SaveDataType.EmailAddress;
SaveType &= ~SaveDataType.Username;
}
}
}
}

View File

@@ -76,6 +76,8 @@ namespace keepass2android.services.AutofillBase
responseBuilder.AddDataset(entryDataset);
AddQueryDataset(query, isManual, autofillIds, responseBuilder, !hasEntryDataset);
responseBuilder.SetSaveInfo(new SaveInfo.Builder(parser.AutofillFields.SaveType,
parser.AutofillFields.GetAutofillIds()).Build());
callback.OnSuccess(responseBuilder.Build());
}
@@ -122,10 +124,29 @@ namespace keepass2android.services.AutofillBase
public override void OnSaveRequest(SaveRequest request, SaveCallback callback)
{
//TODO implement save
callback.OnFailure("Saving data is currently not implemented in Keepass2Android.");
var structure = request.FillContexts?.LastOrDefault()?.Structure;
if (structure == null)
{
return;
}
var parser = new StructureParser(this, structure);
string query = parser.ParseForSave();
try
{
HandleSaveRequest(parser, query);
callback.OnSuccess();
}
catch (Exception e)
{
callback.OnFailure(e.Message);
}
}
protected abstract void HandleSaveRequest(StructureParser parser, string query);
public override void OnConnected()
{

View File

@@ -6,6 +6,8 @@ using Android.Content;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Autofill;
using keepass2android.services.AutofillBase.model;
using FilledAutofillFieldCollection = keepass2android.services.AutofillBase.model.FilledAutofillFieldCollection;
namespace keepass2android.services.AutofillBase
@@ -61,32 +63,33 @@ namespace keepass2android.services.AutofillBase
ParseLocked(forFill, isManualRequest, view, ref webDomain);
}
if (AutofillFields.Empty)
List<AssistStructure.ViewNode> passwordFields = new List<AssistStructure.ViewNode>();
List<AssistStructure.ViewNode> usernameFields = new List<AssistStructure.ViewNode>();
if (AutofillFields.Empty)
{
var passwordFields = _editTextsWithoutHint
.Where(IsPassword).ToList();
passwordFields = _editTextsWithoutHint.Where(IsPassword).ToList();
if (!passwordFields.Any())
{
passwordFields = _editTextsWithoutHint.Where(HasPasswordHint).ToList();
}
foreach (var passwordField in passwordFields)
{
AutofillFields.Add(new AutofillFieldMetadata(passwordField, new[] { View.AutofillHintPassword }));
var usernameField = _editTextsWithoutHint.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault();
if (usernameField != null)
{
AutofillFields.Add(new AutofillFieldMetadata(usernameField, new[] {View.AutofillHintUsername}));
usernameFields.Add(usernameField);
}
}
//for some pages with two-step login, we don't see a password field and don't display the autofill for non-manual requests. But if the user forces autofill,
//let's assume it is a username field:
if (isManualRequest && !passwordFields.Any() && _editTextsWithoutHint.Count == 1)
{
AutofillFields.Add(new AutofillFieldMetadata(_editTextsWithoutHint.First(), new[] { View.AutofillHintUsername }));
usernameFields.Add(_editTextsWithoutHint.First());
}
}
@@ -97,12 +100,30 @@ namespace keepass2android.services.AutofillBase
{
if (editText.IsFocused)
{
AutofillFields.Add(new AutofillFieldMetadata(editText, new[] { IsPassword(editText) || HasPasswordHint(editText) ? View.AutofillHintPassword : View.AutofillHintUsername }));
if (IsPassword(editText) || HasPasswordHint(editText))
passwordFields.Add(editText);
else
usernameFields.Add(editText);
break;
}
}
}
if (forFill)
{
foreach (var pf in passwordFields)
AutofillFields.Add(new AutofillFieldMetadata(pf, new[] { View.AutofillHintPassword }));
foreach (var uf in usernameFields)
AutofillFields.Add(new AutofillFieldMetadata(uf, new[] { View.AutofillHintUsername }));
}
else
{
foreach (var pf in passwordFields)
ClientFormData.Add(new FilledAutofillField(pf, new[] { View.AutofillHintPassword }));
foreach (var uf in usernameFields)
ClientFormData.Add(new FilledAutofillField(uf, new[] { View.AutofillHintUsername }));
}
@@ -182,10 +203,9 @@ namespace keepass2android.services.AutofillBase
}
else
{
//TODO implement save
throw new NotImplementedException("TODO: Port and use AutoFill hints");
//ClientFormData.Add(new FilledAutofillField(viewNode));
}
FilledAutofillField filledAutofillField = new FilledAutofillField(viewNode);
ClientFormData.Add(filledAutofillField);
}
}
else
{

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Android.App.Assist;
using Android.Views.Autofill;
using KeePassLib.Utility;
namespace keepass2android.services.AutofillBase.model
{
@@ -11,6 +12,17 @@ namespace keepass2android.services.AutofillBase.model
public long? DateValue { get; set; }
public bool? ToggleValue { get; set; }
public string ValueToString()
{
if (DateValue != null)
{
return TimeUtil.ConvertUnixTime((long)DateValue / 1000.0).ToLongDateString();
}
if (ToggleValue != null)
return ToggleValue.ToString();
return TextValue;
}
/// <summary>
/// returns the autofill hints for the filled field. These are always lowercased for simpler string comparison.
/// </summary>
@@ -33,11 +45,17 @@ namespace keepass2android.services.AutofillBase.model
public FilledAutofillField()
{}
public FilledAutofillField(AssistStructure.ViewNode viewNode)
public FilledAutofillField(AssistStructure.ViewNode viewNode)
: this(viewNode, viewNode.GetAutofillHints())
{
}
public FilledAutofillField(AssistStructure.ViewNode viewNode, string[] hints)
{
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(viewNode.GetAutofillHints());
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(hints);
List<string> hintList = new List<string>();
string nextHint = null;
@@ -81,9 +99,8 @@ namespace keepass2android.services.AutofillBase.model
CommonUtil.loge($"Invalid hint: {rawHints[i]}");
}
}
AutofillHints = hintList.ToArray();
//TODO port updated FilledAutofillField for saving
AutofillHints = AutofillHintsHelper.ConvertToCanonicalHints(hintList.ToArray()).ToArray();
AutofillValue autofillValue = viewNode.AutofillValue;
if (autofillValue != null)
{

View File

@@ -44,7 +44,7 @@ namespace keepass2android.services.AutofillBase.model
{
if (AutofillHintsHelper.IsSupportedHint(hint))
{
HintMap.Add(hint, filledAutofillField);
HintMap.TryAdd(hint, filledAutofillField);
}
else
{

View File

@@ -119,6 +119,14 @@ namespace keepass2android.services.Kp2aAutofill
private static readonly Dictionary<string, string> keyToHint = BuildKeyToHint();
public static string GetKp2aKeyFromHint(string canonicalHint)
{
var key = keyToHint.FirstOrDefault(p => p.Value.Equals(canonicalHint, StringComparison.OrdinalIgnoreCase)).Key;
if (string.IsNullOrWhiteSpace(key))
return canonicalHint;
return key;
}
private static Dictionary<string, string> BuildKeyToHint()
{
var result = new Dictionary<string, string>
@@ -127,15 +135,15 @@ namespace keepass2android.services.Kp2aAutofill
{PwDefs.PasswordField, View.AutofillHintPassword},
{PwDefs.UrlField, W3cHints.URL},
{
Android.App.Application.Context.GetString(Resource.String.TemplateField_CreditCard_CVV),
Application.Context.GetString(Resource.String.TemplateField_CreditCard_CVV),
View.AutofillHintCreditCardSecurityCode
},
{
Android.App.Application.Context.GetString(Resource.String.TemplateField_CreditCard_Owner),
Application.Context.GetString(Resource.String.TemplateField_CreditCard_Owner),
W3cHints.CC_NAME
},
{Android.App.Application.Context.GetString(Resource.String.TemplateField_Number), View.AutofillHintCreditCardNumber},
{Android.App.Application.Context.GetString(Resource.String.TemplateField_IdCard_Name), View.AutofillHintName},
{Application.Context.GetString(Resource.String.TemplateField_Number), View.AutofillHintCreditCardNumber},
{Application.Context.GetString(Resource.String.TemplateField_IdCard_Name), View.AutofillHintName},
};
return result;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Android;
using Android.App;
using Android.Content;
@@ -6,6 +7,10 @@ using Android.Runtime;
using keepass2android.services.AutofillBase;
using keepass2android.services.AutofillBase.model;
using keepass2android.services.Kp2aAutofill;
using Keepass2android.Pluginsdk;
using KeePassLib;
using KeePassLib.Utility;
using Org.Json;
using AutofillServiceBase = keepass2android.services.AutofillBase.AutofillServiceBase;
namespace keepass2android.services
@@ -34,6 +39,37 @@ namespace keepass2android.services
return null;
}
protected override void HandleSaveRequest(StructureParser parser, string query)
{
var intent = new Intent(this, typeof(FileSelectActivity));
Dictionary<string, string> outputFields = new Dictionary<string, string>();
foreach (var p in parser.ClientFormData.HintMap)
{
CommonUtil.logd(p.Key + " = " + p.Value.ValueToString());
outputFields.TryAdd(ChooseForAutofillActivity.GetKp2aKeyFromHint(p.Key), p.Value.ValueToString());
}
if (query != null)
outputFields.TryAdd(PwDefs.UrlField, query);
JSONObject jsonOutput = new JSONObject(outputFields);
var jsonOutputStr = jsonOutput.ToString();
intent.PutExtra(Strings.ExtraEntryOutputData, jsonOutputStr);
JSONArray jsonProtectedFields = new JSONArray(
(System.Collections.ICollection)new string[]{});
intent.PutExtra(Strings.ExtraProtectedFieldsList, jsonProtectedFields.ToString());
intent.PutExtra(AppTask.AppTaskKey, "CreateEntryThenCloseTask");
intent.PutExtra(CreateEntryThenCloseTask.ShowUserNotificationsKey, "false");
StartActivity(intent);
}
public override IAutofillIntentBuilder IntentBuilder => new Kp2aAutofillIntentBuilder();
}
}