public override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) { bool isManual = (request.Flags & FillRequest.FlagManualRequest) != 0; CommonUtil.logd("onFillRequest " + (isManual ? "manual" : "auto")); var structure = request.FillContexts[request.FillContexts.Count - 1].Structure; //TODO support package signature verification as soon as this is supported in Keepass storage var clientState = request.ClientState; CommonUtil.logd("onFillRequest(): data=" + CommonUtil.BundleToString(clientState)); cancellationSignal.CancelEvent += (sender, e) => { Log.Warn(CommonUtil.Tag, "Cancel autofill not implemented yet."); }; // Parse AutoFill data in Activity string query = null; var parser = new StructureParser(this, structure); try { query = parser.ParseForFill(isManual); } catch (Java.Lang.SecurityException e) { Log.Warn(CommonUtil.Tag, "Security exception handling request"); callback.OnFailure(e.Message); return; } AutofillFieldMetadataCollection autofillFields = parser.AutofillFields; var autofillIds = autofillFields.GetAutofillIds(); if (autofillIds.Length != 0 && CanAutofill(query, isManual)) { var responseBuilder = new FillResponse.Builder(); var entryDataset = AddEntryDataset(query, parser); bool hasEntryDataset = entryDataset != null; if (entryDataset != null) { responseBuilder.AddDataset(entryDataset); } AddQueryDataset(query, isManual, autofillIds, responseBuilder, !hasEntryDataset); AddDisableDataset(query, autofillIds, responseBuilder, isManual); responseBuilder.SetSaveInfo(new SaveInfo.Builder(parser.AutofillFields.SaveType, parser.AutofillFields.GetAutofillIds()).Build()); callback.OnSuccess(responseBuilder.Build()); } else { callback.OnSuccess(null); } }
public static string[] FilterForSupportedHints(string[] hints) { var filteredHints = new string[hints.Length]; int i = 0; foreach (var hint in hints) { if (IsSupportedHint(hint)) { filteredHints[i++] = hint; } else { CommonUtil.logd("Invalid autofill hint: " + hint); } } var finalFilteredHints = new string[i]; Array.Copy(filteredHints, 0, finalFilteredHints, 0, i); return(finalFilteredHints); }
public override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) { bool isManual = (request.Flags & FillRequest.FlagManualRequest) != 0; CommonUtil.logd("onFillRequest " + (isManual ? "manual" : "auto")); var structure = request.FillContexts[request.FillContexts.Count - 1].Structure; //TODO support package signature verification as soon as this is supported in Keepass storage var clientState = request.ClientState; CommonUtil.logd("onFillRequest(): data=" + CommonUtil.BundleToString(clientState)); cancellationSignal.CancelEvent += (sender, e) => { Log.Warn(CommonUtil.Tag, "Cancel autofill not implemented yet."); }; // Parse AutoFill data in Activity StructureParser.AutofillTargetId query = null; var parser = new StructureParser(this, structure); try { query = parser.ParseForFill(isManual); } catch (Java.Lang.SecurityException e) { Log.Warn(CommonUtil.Tag, "Security exception handling request"); callback.OnFailure(e.Message); return; } AutofillFieldMetadataCollection autofillFields = parser.AutofillFields; var autofillIds = autofillFields.GetAutofillIds(); if (autofillIds.Length != 0 && CanAutofill(query, isManual)) { var responseBuilder = new FillResponse.Builder(); Dataset entryDataset = null; if (query.IncompatiblePackageAndDomain == false) { //domain and package are compatible. Use Domain if available and package otherwise. Can fill without warning. entryDataset = BuildEntryDataset(query.DomainOrPackage, query.WebDomain, query.PackageName, autofillIds, parser, DisplayWarning.None); } else { //domain or package are incompatible. Don't show the entry. (Tried to do so first but behavior was not consistent) //entryDataset = BuildEntryDataset(query.WebDomain, query.WebDomain, query.PackageName, autofillIds, parser, DisplayWarning.FillDomainInUntrustedApp); } bool hasEntryDataset = entryDataset != null; if (entryDataset != null) { responseBuilder.AddDataset(entryDataset); } if (query.WebDomain != null) { AddQueryDataset(query.WebDomain, query.WebDomain, query.PackageName, isManual, autofillIds, responseBuilder, !hasEntryDataset, query.IncompatiblePackageAndDomain ? DisplayWarning.FillDomainInUntrustedApp : DisplayWarning.None); } else { AddQueryDataset(query.PackageNameWithPseudoSchema, query.WebDomain, query.PackageName, isManual, autofillIds, responseBuilder, !hasEntryDataset, DisplayWarning.None); } AddDisableDataset(query.DomainOrPackage, autofillIds, responseBuilder, isManual); if (PreferenceManager.GetDefaultSharedPreferences(this) .GetBoolean(GetString(Resource.String.OfferSaveCredentials_key), true)) { if (!CompatBrowsers.Contains(parser.PackageId)) { responseBuilder.SetSaveInfo(new SaveInfo.Builder(parser.AutofillFields.SaveType, parser.AutofillFields.GetAutofillIds()).Build()); } } callback.OnSuccess(responseBuilder.Build()); } else { callback.OnSuccess(null); } }
public override void OnDisconnected() { CommonUtil.logd("onDisconnected"); }
void ParseLocked(bool forFill, bool isManualRequest, AssistStructure.ViewNode viewNode, ref string validWebdomain) { String webDomain = viewNode.WebDomain; if (webDomain != null) { Log.Debug(CommonUtil.Tag, $"child web domain: {webDomain}"); if (!string.IsNullOrEmpty(validWebdomain)) { if (webDomain == validWebdomain) { throw new Java.Lang.SecurityException($"Found multiple web domains: valid= {validWebdomain}, child={webDomain}"); } } else { validWebdomain = webDomain; } } string[] viewHints = viewNode.GetAutofillHints(); if (viewHints != null && viewHints.Length == 1 && viewHints.First() == "off" && viewNode.IsFocused && isManualRequest) { viewHints[0] = "on"; } CommonUtil.logd("viewHints=" + viewHints); CommonUtil.logd("class=" + viewNode.ClassName); CommonUtil.logd("tag=" + (viewNode?.HtmlInfo?.Tag ?? "(null)")); if (viewNode?.HtmlInfo?.Tag == "input") { foreach (var p in viewNode.HtmlInfo.Attributes) { CommonUtil.logd("attr=" + p.First + "/" + p.Second); } } if (viewHints != null && viewHints.Length > 0 && viewHints.First() != "on" /*if hint is "on", treat as if there is no hint*/) { if (forFill) { AutofillFields.Add(new AutofillFieldMetadata(viewNode)); } else { //TODO implement save throw new NotImplementedException("TODO: Port and use AutoFill hints"); //ClientFormData.Add(new FilledAutofillField(viewNode)); } } else { if (viewNode.ClassName == "android.widget.EditText" || viewNode?.HtmlInfo?.Tag == "input") { _editTextsWithoutHint.Add(viewNode); } } var childrenSize = viewNode.ChildCount; if (childrenSize > 0) { for (int i = 0; i < childrenSize; i++) { ParseLocked(forFill, isManualRequest, viewNode.GetChildAt(i), ref validWebdomain); } } }
public override void OnDisconnected() { _lockTime = DateTime.MinValue; CommonUtil.logd("onDisconnected"); }
public override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) { bool isManual = (request.Flags & FillRequest.FlagManualRequest) != 0; CommonUtil.logd("onFillRequest " + (isManual ? "manual" : "auto")); var structure = request.FillContexts.Last().Structure; if (_lockTime + _lockTimeout < DateTime.Now) { _lockTime = DateTime.Now; //TODO support package signature verification as soon as this is supported in Keepass storage var clientState = request.ClientState; CommonUtil.logd("onFillRequest(): data=" + CommonUtil.BundleToString(clientState)); cancellationSignal.CancelEvent += (sender, e) => { Kp2aLog.Log("Cancel autofill not implemented yet."); _lockTime = DateTime.MinValue; }; // Parse AutoFill data in Activity StructureParser.AutofillTargetId query = null; var parser = new StructureParser(this, structure); try { query = parser.ParseForFill(isManual); } catch (Java.Lang.SecurityException e) { Log.Warn(CommonUtil.Tag, "Security exception handling request"); callback.OnFailure(e.Message); return; } AutofillFieldMetadataCollection autofillFields = parser.AutofillFields; InlineSuggestionsRequest inlineSuggestionsRequest = null; IList <InlinePresentationSpec> inlinePresentationSpecs = null; if (((int)Build.VERSION.SdkInt >= 30) && (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(GetString(Resource.String.InlineSuggestions_key), true))) { inlineSuggestionsRequest = request.InlineSuggestionsRequest; inlinePresentationSpecs = inlineSuggestionsRequest?.InlinePresentationSpecs; } var autofillIds = autofillFields.GetAutofillIds(); if (autofillIds.Length != 0 && CanAutofill(query, isManual)) { var responseBuilder = new FillResponse.Builder(); bool hasEntryDataset = false; IList <Dataset> entryDatasets = new List <Dataset>(); if (query.IncompatiblePackageAndDomain == false) { Kp2aLog.Log("AF: (query.IncompatiblePackageAndDomain == false)"); //domain and package are compatible. Use Domain if available and package otherwise. Can fill without warning. entryDatasets = BuildEntryDatasets(query.DomainOrPackage, query.WebDomain, query.PackageName, autofillIds, parser, DisplayWarning.None, inlinePresentationSpecs ).Where(ds => ds != null).ToList(); if (entryDatasets.Count > inlineSuggestionsRequest?.MaxSuggestionCount - 2 /*disable dataset and query*/) { //we have too many elements. disable inline suggestions inlinePresentationSpecs = null; entryDatasets = BuildEntryDatasets(query.DomainOrPackage, query.WebDomain, query.PackageName, autofillIds, parser, DisplayWarning.None, null ).Where(ds => ds != null).ToList(); } foreach (var entryDataset in entryDatasets ) { Kp2aLog.Log("AF: Got EntryDataset " + (entryDataset == null)); responseBuilder.AddDataset(entryDataset); hasEntryDataset = true; } } { if (query.WebDomain != null) { AddQueryDataset(query.WebDomain, query.WebDomain, query.PackageName, isManual, autofillIds, responseBuilder, !hasEntryDataset, query.IncompatiblePackageAndDomain ? DisplayWarning.FillDomainInUntrustedApp : DisplayWarning.None, AutofillHelper.ExtractSpec(inlinePresentationSpecs, entryDatasets.Count)); } else { AddQueryDataset(query.PackageNameWithPseudoSchema, query.WebDomain, query.PackageName, isManual, autofillIds, responseBuilder, !hasEntryDataset, DisplayWarning.None, AutofillHelper.ExtractSpec(inlinePresentationSpecs, entryDatasets.Count)); } } if (!PreferenceManager.GetDefaultSharedPreferences(this) .GetBoolean(GetString(Resource.String.NoAutofillDisabling_key), false)) { AddDisableDataset(query.DomainOrPackage, autofillIds, responseBuilder, isManual, AutofillHelper.ExtractSpec(inlinePresentationSpecs, entryDatasets.Count)); } if (PreferenceManager.GetDefaultSharedPreferences(this) .GetBoolean(GetString(Resource.String.OfferSaveCredentials_key), true)) { if (!CompatBrowsers.Contains(parser.PackageId)) { responseBuilder.SetSaveInfo(new SaveInfo.Builder(parser.AutofillFields.SaveType, parser.AutofillFields.GetAutofillIds()).Build()); } } Kp2aLog.Log("return autofill success"); callback.OnSuccess(responseBuilder.Build()); } else { Kp2aLog.Log("cannot autofill"); callback.OnSuccess(null); } } else { Kp2aLog.Log("Ignoring onFillRequest as there is another request going on."); } }
/// <summary> /// Traverse AssistStructure and add ViewNode metadata to a flat list. /// </summary> /// <returns>The parse.</returns> /// <param name="forFill">If set to <c>true</c> for fill.</param> /// <param name="isManualRequest"></param> string Parse(bool forFill, bool isManualRequest) { CommonUtil.logd("Parsing structure for " + Structure.ActivityComponent); var nodes = Structure.WindowNodeCount; ClientFormData = new FilledAutofillFieldCollection(); String webDomain = null; _editTextsWithoutHint.Clear(); for (int i = 0; i < nodes; i++) { var node = Structure.GetWindowNodeAt(i); var view = node.RootViewNode; ParseLocked(forFill, isManualRequest, view, ref webDomain); } List <AssistStructure.ViewNode> passwordFields = new List <AssistStructure.ViewNode>(); List <AssistStructure.ViewNode> usernameFields = new List <AssistStructure.ViewNode>(); if (AutofillFields.Empty) { passwordFields = _editTextsWithoutHint.Where(IsPassword).ToList(); if (!passwordFields.Any()) { passwordFields = _editTextsWithoutHint.Where(HasPasswordHint).ToList(); } foreach (var passwordField in passwordFields) { var usernameField = _editTextsWithoutHint.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault(); if (usernameField != null) { 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) { usernameFields.Add(_editTextsWithoutHint.First()); } } //force focused fields to be included in autofill fields when request was triggered manually. This allows to fill fields which are "off" or don't have a hint (in case there are hints) if (isManualRequest) { foreach (AssistStructure.ViewNode editText in _editTextsWithoutHint) { if (editText.IsFocused) { if (IsPassword(editText) || HasPasswordHint(editText)) { passwordFields.Add(editText); } else { usernameFields.Add(editText); } break; } } } if (forFill) { foreach (var uf in usernameFields) { AutofillFields.Add(new AutofillFieldMetadata(uf, new[] { View.AutofillHintUsername })); } foreach (var pf in passwordFields) { AutofillFields.Add(new AutofillFieldMetadata(pf, new[] { View.AutofillHintPassword })); } } else { foreach (var uf in usernameFields) { ClientFormData.Add(new FilledAutofillField(uf, new[] { View.AutofillHintUsername })); } foreach (var pf in passwordFields) { ClientFormData.Add(new FilledAutofillField(pf, new[] { View.AutofillHintPassword })); } } String packageName = Structure.ActivityComponent.PackageName; if (!string.IsNullOrEmpty(webDomain)) { bool valid = Kp2aDigitalAssetLinksDataSource.Instance.IsValid(mContext, webDomain, packageName); if (!valid) { CommonUtil.loge($"DAL verification failed for {packageName}/{webDomain}"); webDomain = null; } } if (string.IsNullOrEmpty(webDomain)) { webDomain = "androidapp://" + packageName; CommonUtil.logd("no web domain. Using package name."); } return(webDomain); }
void ParseLocked(bool forFill, bool isManualRequest, AssistStructure.ViewNode viewNode, ref string validWebdomain) { String webDomain = viewNode.WebDomain; if ((PackageId == null) && (!string.IsNullOrWhiteSpace(viewNode.IdPackage)) && (viewNode.IdPackage != "android")) { PackageId = viewNode.IdPackage; } DomainName outDomain; if (DomainName.TryParse(webDomain, domainSuffixParserCache, out outDomain)) { webDomain = outDomain.RegisterableDomainName; } if (webDomain != null) { if (!string.IsNullOrEmpty(validWebdomain)) { if (webDomain != validWebdomain) { throw new Java.Lang.SecurityException($"Found multiple web domains: valid= {validWebdomain}, child={webDomain}"); } } else { validWebdomain = webDomain; } } string[] viewHints = viewNode.GetAutofillHints(); if (viewHints != null && viewHints.Length == 1 && viewHints.First() == "off" && viewNode.IsFocused && isManualRequest) { viewHints[0] = "on"; } if (viewHints != null && viewHints.Any()) { CommonUtil.logd("viewHints=" + viewHints); CommonUtil.logd("class=" + viewNode.ClassName); CommonUtil.logd("tag=" + (viewNode?.HtmlInfo?.Tag ?? "(null)")); } if (viewNode?.HtmlInfo?.Tag == "input") { foreach (var p in viewNode.HtmlInfo.Attributes) { CommonUtil.logd("attr=" + p.First + "/" + p.Second); } } if (viewHints != null && viewHints.Length > 0 && viewHints.First() != "on" /*if hint is "on", treat as if there is no hint*/) { if (forFill) { AutofillFields.Add(new AutofillFieldMetadata(viewNode)); } else { FilledAutofillField filledAutofillField = new FilledAutofillField(viewNode); ClientFormData.Add(filledAutofillField); } } else { if (viewNode.ClassName == "android.widget.EditText" || viewNode?.HtmlInfo?.Tag == "input") { _editTextsWithoutHint.Add(viewNode); } } var childrenSize = viewNode.ChildCount; if (childrenSize > 0) { for (int i = 0; i < childrenSize; i++) { ParseLocked(forFill, isManualRequest, viewNode.GetChildAt(i), ref validWebdomain); } } }
/// <summary> /// Traverse AssistStructure and add ViewNode metadata to a flat list. /// </summary> /// <returns>The parse.</returns> /// <param name="forFill">If set to <c>true</c> for fill.</param> /// <param name="isManualRequest"></param> AutofillTargetId Parse(bool forFill, bool isManualRequest) { AutofillTargetId result = new AutofillTargetId(); CommonUtil.logd("Parsing structure for " + Structure.ActivityComponent); var nodes = Structure.WindowNodeCount; ClientFormData = new FilledAutofillFieldCollection(); String webDomain = null; _editTextsWithoutHint.Clear(); for (int i = 0; i < nodes; i++) { var node = Structure.GetWindowNodeAt(i); var view = node.RootViewNode; ParseLocked(forFill, isManualRequest, view, ref webDomain); } List <AssistStructure.ViewNode> passwordFields = new List <AssistStructure.ViewNode>(); List <AssistStructure.ViewNode> usernameFields = new List <AssistStructure.ViewNode>(); if (AutofillFields.Empty) { passwordFields = _editTextsWithoutHint.Where(IsPassword).ToList(); if (!passwordFields.Any()) { passwordFields = _editTextsWithoutHint.Where(HasPasswordHint).ToList(); } usernameFields = _editTextsWithoutHint.Where(HasUsernameHint).ToList(); if (usernameFields.Any() == false) { foreach (var passwordField in passwordFields) { var usernameField = _editTextsWithoutHint .TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault(); if (usernameField != null) { usernameFields.Add(usernameField); } } } if (usernameFields.Any() == false) { //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) { usernameFields.Add(_editTextsWithoutHint.First()); } } } //force focused fields to be included in autofill fields when request was triggered manually. This allows to fill fields which are "off" or don't have a hint (in case there are hints) if (isManualRequest) { foreach (AssistStructure.ViewNode editText in _editTextsWithoutHint) { if (editText.IsFocused) { if (IsPassword(editText) || HasPasswordHint(editText)) { passwordFields.Add(editText); } else { usernameFields.Add(editText); } break; } } } if (forFill) { foreach (var uf in usernameFields) { AutofillFields.Add(new AutofillFieldMetadata(uf, new[] { View.AutofillHintUsername })); } foreach (var pf in passwordFields) { AutofillFields.Add(new AutofillFieldMetadata(pf, new[] { View.AutofillHintPassword })); } } else { foreach (var uf in usernameFields) { ClientFormData.Add(new FilledAutofillField(uf, new[] { View.AutofillHintUsername })); } foreach (var pf in passwordFields) { ClientFormData.Add(new FilledAutofillField(pf, new[] { View.AutofillHintPassword })); } } result.WebDomain = webDomain; result.PackageName = Structure.ActivityComponent.PackageName; if (!string.IsNullOrEmpty(webDomain) && !PreferenceManager.GetDefaultSharedPreferences(mContext).GetBoolean(mContext.GetString(Resource.String.NoDalVerification_key), false)) { result.IncompatiblePackageAndDomain = !kp2aDigitalAssetLinksDataSource.IsTrustedLink(webDomain, result.PackageName); if (result.IncompatiblePackageAndDomain) { CommonUtil.loge($"DAL verification failed for {result.PackageName}/{result.WebDomain}"); } } else { result.IncompatiblePackageAndDomain = false; } return(result); }
public override void OnDisconnected() { _lock.Set(false); CommonUtil.logd("onDisconnected"); }
public override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) { bool isManual = (request.Flags & FillRequest.FlagManualRequest) != 0; CommonUtil.logd("onFillRequest " + (isManual ? "manual" : "auto")); var structure = request.FillContexts[request.FillContexts.Count - 1].Structure; //TODO support package signature verification as soon as this is supported in Keepass storage var clientState = request.ClientState; CommonUtil.logd("onFillRequest(): data=" + CommonUtil.BundleToString(clientState)); cancellationSignal.CancelEvent += (sender, e) => { Log.Warn(CommonUtil.Tag, "Cancel autofill not implemented yet."); }; // Parse AutoFill data in Activity string query = null; var parser = new StructureParser(this, structure); try { query = parser.ParseForFill(isManual); } catch (Java.Lang.SecurityException e) { Log.Warn(CommonUtil.Tag, "Security exception handling request"); callback.OnFailure(e.Message); return; } AutofillFieldMetadataCollection autofillFields = parser.AutofillFields; bool responseAuth = true; var autofillIds = autofillFields.GetAutofillIds(); 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")); } responseBuilder.AddDataset(datasetBuilder.Build()); callback.OnSuccess(responseBuilder.Build()); } else { var datasetAuth = true; var response = AutofillHelper.NewResponse(this, datasetAuth, autofillFields, null, IntentBuilder); callback.OnSuccess(response); } }