Skip to content

Commit 5edc070

Browse files
committed
show entry notification when autofilling an entry with TOTP (to allow copying TOTP to clipboard)
fixes #1272
1 parent 64355a3 commit 5edc070

File tree

11 files changed

+248
-79
lines changed

11 files changed

+248
-79
lines changed

src/Kp2aBusinessLogic/SearchDbHelper.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,29 @@ public PwGroup SearchForExactUrl (Database database, string url)
9191

9292
}
9393

94-
private static String ExtractHost(String url)
94+
public PwGroup SearchForUuid(Database database, string uuid)
95+
{
96+
SearchParameters sp = SearchParameters.None;
97+
sp.SearchInUuids = true;
98+
sp.SearchString = uuid;
99+
100+
if (sp.RegularExpression) // Validate regular expression
101+
{
102+
new Regex(sp.SearchString);
103+
}
104+
105+
string strGroupName = _app.GetResourceString(UiStringKey.search_results);
106+
PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) { IsVirtual = true };
107+
108+
PwObjectList<PwEntry> listResults = pgResults.Entries;
109+
110+
database.Root.SearchEntries(sp, listResults, new NullStatusLogger());
111+
112+
return pgResults;
113+
114+
}
115+
116+
private static String ExtractHost(String url)
95117
{
96118
return UrlUtil.GetHost(url.Trim());
97119
}

src/Kp2aBusinessLogic/database/Database.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,17 @@ public PwGroup SearchForExactUrl(String url) {
174174
PwGroup group = SearchHelper.SearchForExactUrl(this, url);
175175

176176
return group;
177-
178-
}
179177

180-
public PwGroup SearchForHost(String url, bool allowSubdomains) {
178+
}
179+
public PwGroup SearchForUuid(String uuid)
180+
{
181+
PwGroup group = SearchHelper.SearchForUuid(this, uuid);
182+
183+
return group;
184+
185+
}
186+
187+
public PwGroup SearchForHost(String url, bool allowSubdomains) {
181188
PwGroup group = SearchHelper.SearchForHost(this, url, allowSubdomains);
182189

183190
return group;

src/keepass2android/EntryActivity.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,10 +488,11 @@ protected override void OnCreate(Bundle savedInstanceState)
488488
_pluginFieldReceiver = new PluginFieldReceiver(this);
489489
RegisterReceiver(_pluginFieldReceiver, new IntentFilter(Strings.ActionSetEntryField));
490490

491-
new Thread(NotifyPluginsOnOpen).Start();
491+
var notifyPluginsOnOpenThread = new Thread(NotifyPluginsOnOpen);
492+
notifyPluginsOnOpenThread.Start();
492493

493494
//the rest of the things to do depends on the current app task:
494-
AppTask.CompleteOnCreateEntryActivity(this);
495+
AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread);
495496
}
496497

497498
private void RemoveFromHistory()

src/keepass2android/ShareUrlResults.cs

Lines changed: 89 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public static void Launch(Activity act, SearchUrlTask task, ActivityLaunchMode l
6363
launchMode.Launch(act, i);
6464
}
6565

66+
public static void Launch(Activity act, OpenSpecificEntryTask task, ActivityLaunchMode launchMode)
67+
{
68+
Intent i = new Intent(act, typeof(ShareUrlResults));
69+
task.ToIntent(i);
70+
launchMode.Launch(act, i);
71+
}
6672

6773
public override bool IsSearchResult
6874
{
@@ -83,9 +89,8 @@ protected override void OnCreate(Bundle savedInstanceState)
8389

8490
if (App.Kp2a.DatabaseIsUnlocked)
8591
{
86-
var searchUrlTask = ((SearchUrlTask)AppTask);
87-
String searchUrl = searchUrlTask.UrlToSearchFor;
88-
Query(searchUrl, searchUrlTask.AutoReturnFromQuery);
92+
93+
Query();
8994
}
9095
// else: LockCloseListActivity.OnResume will trigger a broadcast (LockDatabase) which will cause the activity to be finished.
9196

@@ -99,12 +104,25 @@ protected override void OnSaveInstanceState(Bundle outState)
99104
AppTask.ToBundle(outState);
100105
}
101106

102-
private void Query(string url, bool autoReturnFromQuery)
107+
private void Query()
103108
{
104-
109+
bool canAutoReturnFromQuery = true;
110+
bool shouldAutoReturnFromQuery = true;
105111
try
106112
{
107-
Group = GetSearchResultsForUrl(url);
113+
if (AppTask is SearchUrlTask searchUrlTask)
114+
{
115+
String searchUrl = searchUrlTask.UrlToSearchFor;
116+
canAutoReturnFromQuery = searchUrlTask.AutoReturnFromQuery;
117+
shouldAutoReturnFromQuery = PreferenceManager.GetDefaultSharedPreferences(this)
118+
.GetBoolean(GetString(Resource.String.AutoReturnFromQuery_key), true);
119+
Group = GetSearchResultsForUrl(searchUrl);
120+
}
121+
else if (AppTask is OpenSpecificEntryTask openEntryTask)
122+
{
123+
Group = GetSearchResultsForUuid(openEntryTask.EntryUuid);
124+
}
125+
108126
} catch (Exception e)
109127
{
110128
Toast.MakeText(this, e.Message, ToastLength.Long).Show();
@@ -114,7 +132,7 @@ private void Query(string url, bool autoReturnFromQuery)
114132
}
115133

116134
//if there is exactly one match: open the entry
117-
if ((Group.Entries.Count() == 1) && autoReturnFromQuery && PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(GetString(Resource.String.AutoReturnFromQuery_key),true))
135+
if ((Group.Entries.Count() == 1) && canAutoReturnFromQuery && shouldAutoReturnFromQuery)
118136
{
119137
LaunchActivityForEntry(Group.Entries.Single(),0);
120138
return;
@@ -131,32 +149,49 @@ private void Query(string url, bool autoReturnFromQuery)
131149
FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment).ListAdapter = new PwGroupListAdapter(this, Group);
132150

133151
View selectOtherEntry = FindViewById (Resource.Id.select_other_entry);
152+
View createUrlEntry = FindViewById(Resource.Id.add_url_entry);
134153

135-
var newTask = new SearchUrlTask() {AutoReturnFromQuery = false, UrlToSearchFor = url};
136-
if (AppTask is SelectEntryTask currentSelectTask)
137-
newTask.ShowUserNotifications = currentSelectTask.ShowUserNotifications;
138-
139-
selectOtherEntry.Click += (sender, e) => {
140-
GroupActivity.Launch (this, newTask, new ActivityLaunchModeRequestCode(0));
154+
if (AppTask is OpenSpecificEntryTask)
155+
{
156+
selectOtherEntry.Visibility = ViewStates.Gone;
157+
createUrlEntry.Visibility = ViewStates.Gone;
158+
}
159+
else
160+
{
161+
var searchUrlTask = AppTask as SearchUrlTask;
162+
String searchUrl = searchUrlTask.UrlToSearchFor;
163+
selectOtherEntry.Visibility = ViewStates.Visible;
164+
165+
var newTask = new SearchUrlTask() { AutoReturnFromQuery = false, UrlToSearchFor = searchUrl };
166+
if (AppTask is SelectEntryTask currentSelectTask)
167+
newTask.ShowUserNotifications = currentSelectTask.ShowUserNotifications;
168+
169+
selectOtherEntry.Click += (sender, e) => {
170+
GroupActivity.Launch(this, newTask, new ActivityLaunchModeRequestCode(0));
171+
172+
};
173+
174+
175+
176+
177+
if (App.Kp2a.OpenDatabases.Any(db => db.CanWrite))
178+
{
179+
createUrlEntry.Visibility = ViewStates.Visible;
180+
createUrlEntry.Click += (sender, e) =>
181+
{
182+
GroupActivity.Launch(this, new CreateEntryThenCloseTask { Url = searchUrl, ShowUserNotifications = (AppTask as SelectEntryTask)?.ShowUserNotifications ?? ShowUserNotificationsMode.Always }, new ActivityLaunchModeRequestCode(0));
183+
Toast.MakeText(this, GetString(Resource.String.select_group_then_add, new Java.Lang.Object[] { GetString(Resource.String.add_entry) }), ToastLength.Long).Show();
184+
};
185+
}
186+
else
187+
{
188+
createUrlEntry.Visibility = ViewStates.Gone;
189+
}
190+
191+
}
141192

142-
};
143193

144-
145-
View createUrlEntry = FindViewById (Resource.Id.add_url_entry);
146194

147-
if (App.Kp2a.OpenDatabases.Any(db => db.CanWrite))
148-
{
149-
createUrlEntry.Visibility = ViewStates.Visible;
150-
createUrlEntry.Click += (sender, e) =>
151-
{
152-
GroupActivity.Launch(this, new CreateEntryThenCloseTask { Url = url, ShowUserNotifications = (AppTask as SelectEntryTask)?.ShowUserNotifications ?? ShowUserNotificationsMode.Always }, new ActivityLaunchModeRequestCode(0));
153-
Toast.MakeText(this, GetString(Resource.String.select_group_then_add, new Java.Lang.Object[] { GetString(Resource.String.add_entry) }), ToastLength.Long).Show();
154-
};
155-
}
156-
else
157-
{
158-
createUrlEntry.Visibility = ViewStates.Gone;
159-
}
160195

161196
Util.MoveBottomBarButtons(Resource.Id.select_other_entry, Resource.Id.add_url_entry, Resource.Id.bottom_bar, this);
162197
}
@@ -201,6 +236,31 @@ public static PwGroup GetSearchResultsForUrl(string url)
201236
return resultsGroup;
202237
}
203238

239+
240+
public static PwGroup GetSearchResultsForUuid(string uuid)
241+
{
242+
PwGroup resultsGroup = null;
243+
foreach (var db in App.Kp2a.OpenDatabases)
244+
{
245+
246+
var resultsForThisDb = db.SearchForUuid(uuid);
247+
248+
if (resultsGroup == null)
249+
{
250+
resultsGroup = resultsForThisDb;
251+
}
252+
else
253+
{
254+
foreach (var entry in resultsForThisDb.Entries)
255+
{
256+
resultsGroup.AddEntry(entry, false, false);
257+
}
258+
}
259+
}
260+
261+
return resultsGroup;
262+
}
263+
204264
public override bool OnSearchRequested()
205265
{
206266
Intent i = new Intent(this, typeof(SearchActivity));

src/keepass2android/Totp/Kp2aTotp.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public ITotpPluginAdapter TryGetAdapter(PwEntryOutput entry)
4646
foreach (ITotpPluginAdapter adapter in _pluginAdapters)
4747
{
4848
TotpData totpData = adapter.GetTotpData(
49-
App.Kp2a.LastOpenedEntry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key),
49+
entry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key),
5050
pair => pair.Value.ReadString()), LocaleManager.LocalizedAppContext, false);
5151
if (totpData.IsTotpEntry)
5252
{

src/keepass2android/app/AppTask.cs

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Android.Widget;
77
using System.Collections.Generic;
88
using System.Linq;
9+
using System.Threading;
910
using KeePassLib;
1011
using KeePassLib.Security;
1112
using KeePassLib.Utility;
@@ -339,7 +340,7 @@ protected void RemoveTaskFromIntent(Activity act)
339340

340341
}
341342

342-
public virtual void CompleteOnCreateEntryActivity(EntryActivity activity)
343+
public virtual void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
343344
{
344345
activity.StartNotificationsService(false);
345346
}
@@ -453,7 +454,7 @@ public override void PopulatePasswordAccessServiceIntent(Intent intent)
453454
intent.PutExtra(UrlToSearchKey, UrlToSearchFor);
454455
}
455456

456-
public override void CompleteOnCreateEntryActivity(EntryActivity activity)
457+
public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
457458
{
458459
if (App.Kp2a.LastOpenedEntry != null)
459460
App.Kp2a.LastOpenedEntry.SearchUrl = UrlToSearchFor;
@@ -462,18 +463,18 @@ public override void CompleteOnCreateEntryActivity(EntryActivity activity)
462463
//if the database is readonly (or no URL exists), don't offer to modify the URL
463464
if ((App.Kp2a.CurrentDb.CanWrite == false) || (String.IsNullOrEmpty(UrlToSearchFor) || keepass2android.ShareUrlResults.GetSearchResultsForUrl(UrlToSearchFor).Entries.Any(e => e == activity.Entry) ))
464465
{
465-
base.CompleteOnCreateEntryActivity(activity);
466+
base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
466467
return;
467468
}
468469

469-
AskAddUrlThenCompleteCreate(activity, UrlToSearchFor);
470+
AskAddUrlThenCompleteCreate(activity, UrlToSearchFor, notifyPluginsOnOpenThread);
470471
}
471472

472473

473474
/// <summary>
474475
/// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding
475476
/// </summary>
476-
public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url)
477+
public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url, Thread notifyPluginsOnOpenThread)
477478
{
478479
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
479480
builder.SetTitle(activity.GetString(Resource.String.AddUrlToEntryDialog_title));
@@ -482,19 +483,58 @@ public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url)
482483

483484
builder.SetPositiveButton(activity.GetString(Resource.String.yes), (dlgSender, dlgEvt) =>
484485
{
485-
activity.AddUrlToEntry(url, (EntryActivity thenActiveActivity) => base.CompleteOnCreateEntryActivity(thenActiveActivity));
486+
activity.AddUrlToEntry(url, (EntryActivity thenActiveActivity) => base.CompleteOnCreateEntryActivity(thenActiveActivity, notifyPluginsOnOpenThread
487+
));
486488
});
487489

488490
builder.SetNegativeButton(activity.GetString(Resource.String.no), (dlgSender, dlgEvt) =>
489491
{
490-
base.CompleteOnCreateEntryActivity(activity);
492+
base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
491493
});
492494

493495
Dialog dialog = builder.Create();
494496
dialog.Show();
495497
}
496498
}
497499

500+
public class OpenSpecificEntryTask : SelectEntryTask
501+
{
502+
public OpenSpecificEntryTask()
503+
{
504+
}
505+
506+
public const String EntryUuidKey = "EntryUuid";
507+
508+
public string EntryUuid
509+
{
510+
get;
511+
set;
512+
}
513+
514+
public override void Setup(Bundle b)
515+
{
516+
base.Setup(b);
517+
EntryUuid = b.GetString(EntryUuidKey);
518+
519+
}
520+
public override IEnumerable<IExtra> Extras
521+
{
522+
get
523+
{
524+
foreach (IExtra e in base.Extras)
525+
yield return e;
526+
527+
yield return new StringExtra { Key = EntryUuidKey, Value = EntryUuid };
528+
}
529+
}
530+
531+
public override void LaunchFirstGroupActivity(Activity act)
532+
{
533+
ShareUrlResults.Launch(act, this, new ActivityLaunchModeRequestCode(0));
534+
}
535+
536+
}
537+
498538

499539
public enum ShowUserNotificationsMode
500540
{
@@ -545,7 +585,7 @@ public override IEnumerable<IExtra> Extras
545585
}
546586
}
547587

548-
public override void CompleteOnCreateEntryActivity(EntryActivity activity)
588+
public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
549589
{
550590
Context ctx = activity;
551591
if (ctx == null)
@@ -563,9 +603,11 @@ public override void CompleteOnCreateEntryActivity(EntryActivity activity)
563603
CopyToClipboardService.CancelNotifications(activity);
564604
}
565605
if (CloseAfterCreate)
566-
{
567-
//close
568-
activity.CloseAfterTaskComplete();
606+
{
607+
//give plugins and TOTP time to do their work:
608+
notifyPluginsOnOpenThread.Join(TimeSpan.FromSeconds(1));
609+
//close
610+
activity.CloseAfterTaskComplete();
569611
}
570612
}
571613
}
@@ -729,10 +771,10 @@ public override void AfterAddNewEntry(EntryEditActivity entryEditActivity, PwEnt
729771
//no need to call Finish here, that's done in EntryEditActivity ("closeOrShowError")
730772
}
731773

732-
public override void CompleteOnCreateEntryActivity(EntryActivity activity)
774+
public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
733775
{
734776
//if the user selects an entry before creating the new one, we're not closing the app
735-
base.CompleteOnCreateEntryActivity(activity);
777+
base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
736778
}
737779
}
738780

0 commit comments

Comments
 (0)