Support Android share-driven credential lookup
This commit is contained in:
@@ -35,6 +35,11 @@
|
|||||||
android:name="org.julianfamily.keepassgo.SharedVaultImportActivity"
|
android:name="org.julianfamily.keepassgo.SharedVaultImportActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar">
|
android:theme="@android:style/Theme.Translucent.NoTitleBar">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
@@ -42,6 +47,13 @@
|
|||||||
<data android:mimeType="application/x-keepass2" />
|
<data android:mimeType="application/x-keepass2" />
|
||||||
<data android:mimeType="application/vnd.keepass" />
|
<data android:mimeType="application/vnd.keepass" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import java.util.ArrayList;
|
|||||||
public final class SharedVaultImportActivity extends Activity {
|
public final class SharedVaultImportActivity extends Activity {
|
||||||
private static final String TAG = "KeePassGOImport";
|
private static final String TAG = "KeePassGOImport";
|
||||||
private static final String DEFAULT_NAME = "shared-vault.kdbx";
|
private static final String DEFAULT_NAME = "shared-vault.kdbx";
|
||||||
|
private static final String PENDING_SHARED_VAULT = "pending-shared-vault.kdbx";
|
||||||
|
private static final String PENDING_SHARED_VAULT_NAME = "pending-shared-vault-name.txt";
|
||||||
|
private static final String PENDING_SHARED_LOOKUP = "pending-shared-lookup.txt";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle state) {
|
protected void onCreate(Bundle state) {
|
||||||
@@ -40,6 +43,16 @@ public final class SharedVaultImportActivity extends Activity {
|
|||||||
|
|
||||||
private void handleIntent(Intent intent) {
|
private void handleIntent(Intent intent) {
|
||||||
logIntent(intent);
|
logIntent(intent);
|
||||||
|
String sharedLookup = resolveSharedLookup(intent);
|
||||||
|
if (!sharedLookup.isEmpty()) {
|
||||||
|
try {
|
||||||
|
persistPendingLookup(sharedLookup);
|
||||||
|
Log.i(TAG, "queued shared lookup target");
|
||||||
|
} catch (IOException | RuntimeException err) {
|
||||||
|
Log.e(TAG, "failed to queue shared lookup target", err);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
Uri uri = resolveSharedUri(intent);
|
Uri uri = resolveSharedUri(intent);
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
Log.i(TAG, "no shared vault URI on intent");
|
Log.i(TAG, "no shared vault URI on intent");
|
||||||
@@ -86,12 +99,35 @@ public final class SharedVaultImportActivity extends Activity {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveSharedLookup(Intent intent) {
|
||||||
|
if (intent == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (Intent.ACTION_SEND.equals(action) && "text/plain".equalsIgnoreCase(intent.getType())) {
|
||||||
|
CharSequence extraText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
|
||||||
|
if (extraText != null) {
|
||||||
|
return extraText.toString().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Intent.ACTION_VIEW.equals(action)) {
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
String scheme = data.getScheme();
|
||||||
|
if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) {
|
||||||
|
return data.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
private void persistPendingImport(Uri uri) throws IOException {
|
private void persistPendingImport(Uri uri) throws IOException {
|
||||||
File dir = new File(getFilesDir(), "keepassgo");
|
File dir = new File(getFilesDir(), "keepassgo");
|
||||||
if (!dir.exists() && !dir.mkdirs()) {
|
if (!dir.exists() && !dir.mkdirs()) {
|
||||||
throw new IOException("failed to create " + dir.getAbsolutePath());
|
throw new IOException("failed to create " + dir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
File pendingFile = new File(dir, "pending-shared-vault.kdbx");
|
File pendingFile = new File(dir, PENDING_SHARED_VAULT);
|
||||||
try (InputStream in = openSharedInputStream(uri)) {
|
try (InputStream in = openSharedInputStream(uri)) {
|
||||||
if (in == null) {
|
if (in == null) {
|
||||||
throw new IOException("failed to open shared vault stream");
|
throw new IOException("failed to open shared vault stream");
|
||||||
@@ -105,12 +141,23 @@ public final class SharedVaultImportActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File nameFile = new File(dir, "pending-shared-vault-name.txt");
|
File nameFile = new File(dir, PENDING_SHARED_VAULT_NAME);
|
||||||
try (FileOutputStream out = new FileOutputStream(nameFile, false)) {
|
try (FileOutputStream out = new FileOutputStream(nameFile, false)) {
|
||||||
out.write(resolveDisplayName(uri).getBytes(StandardCharsets.UTF_8));
|
out.write(resolveDisplayName(uri).getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void persistPendingLookup(String lookup) throws IOException {
|
||||||
|
File dir = new File(getFilesDir(), "keepassgo");
|
||||||
|
if (!dir.exists() && !dir.mkdirs()) {
|
||||||
|
throw new IOException("failed to create " + dir.getAbsolutePath());
|
||||||
|
}
|
||||||
|
File pendingFile = new File(dir, PENDING_SHARED_LOOKUP);
|
||||||
|
try (FileOutputStream out = new FileOutputStream(pendingFile, false)) {
|
||||||
|
out.write(lookup.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private InputStream openSharedInputStream(Uri uri) throws IOException {
|
private InputStream openSharedInputStream(Uri uri) throws IOException {
|
||||||
if ("file".equalsIgnoreCase(uri.getScheme())) {
|
if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||||
String path = uri.getPath();
|
String path = uri.getPath();
|
||||||
|
|||||||
@@ -36,3 +36,24 @@ Expected behavior:
|
|||||||
package as `androidapp://<package>`.
|
package as `androidapp://<package>`.
|
||||||
- The fallback path can therefore fill supported apps that never expose a
|
- The fallback path can therefore fill supported apps that never expose a
|
||||||
browser-style URL bar.
|
browser-style URL bar.
|
||||||
|
|
||||||
|
## Share-Driven Lookup
|
||||||
|
|
||||||
|
User story:
|
||||||
|
|
||||||
|
- When Android shares a login URL or a text snippet containing a login URL into
|
||||||
|
KeePassGO, the app should open into a credential lookup flow instead of only
|
||||||
|
supporting shared `.kdbx` imports.
|
||||||
|
- If the vault is already open, the shared target should immediately narrow the
|
||||||
|
entries view.
|
||||||
|
- If the vault is not open yet, the shared target should survive startup and
|
||||||
|
apply as soon as the vault is unlocked.
|
||||||
|
|
||||||
|
Expected behavior:
|
||||||
|
|
||||||
|
- Android share intents can queue a pending lookup target in addition to shared
|
||||||
|
vault file imports.
|
||||||
|
- KeePassGO normalizes the shared value into a search query that users can
|
||||||
|
immediately act on.
|
||||||
|
- The pending lookup is consumed once and does not keep reappearing on later
|
||||||
|
launches.
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ type statePaths struct {
|
|||||||
AutofillCachePath string
|
AutofillCachePath string
|
||||||
PendingSharedVaultPath string
|
PendingSharedVaultPath string
|
||||||
PendingSharedVaultNamePath string
|
PendingSharedVaultNamePath string
|
||||||
|
PendingSharedLookupPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
type recentVaultRecord struct {
|
type recentVaultRecord struct {
|
||||||
@@ -474,6 +475,8 @@ type ui struct {
|
|||||||
autofillCachePath string
|
autofillCachePath string
|
||||||
pendingSharedVaultPath string
|
pendingSharedVaultPath string
|
||||||
pendingSharedVaultNamePath string
|
pendingSharedVaultNamePath string
|
||||||
|
pendingSharedLookupPath string
|
||||||
|
pendingSharedLookupQuery string
|
||||||
editingEntry bool
|
editingEntry bool
|
||||||
syncDefaultSourceMode syncSourceMode
|
syncDefaultSourceMode syncSourceMode
|
||||||
syncDefaultDirection syncDirection
|
syncDefaultDirection syncDirection
|
||||||
@@ -656,6 +659,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
|||||||
autofillCachePath: paths.AutofillCachePath,
|
autofillCachePath: paths.AutofillCachePath,
|
||||||
pendingSharedVaultPath: paths.PendingSharedVaultPath,
|
pendingSharedVaultPath: paths.PendingSharedVaultPath,
|
||||||
pendingSharedVaultNamePath: paths.PendingSharedVaultNamePath,
|
pendingSharedVaultNamePath: paths.PendingSharedVaultNamePath,
|
||||||
|
pendingSharedLookupPath: paths.PendingSharedLookupPath,
|
||||||
recentVaultGroups: map[string][]string{},
|
recentVaultGroups: map[string][]string{},
|
||||||
recentVaultUsedAt: map[string]time.Time{},
|
recentVaultUsedAt: map[string]time.Time{},
|
||||||
lifecycleAdvancedHidden: true,
|
lifecycleAdvancedHidden: true,
|
||||||
@@ -704,6 +708,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
|||||||
u.showStatusMessage("Some saved remote sign-ins came from an older KeePassGO build. Reopen those remotes and save them in the vault to migrate them.")
|
u.showStatusMessage("Some saved remote sign-ins came from an older KeePassGO build. Reopen those remotes and save them in the vault to migrate them.")
|
||||||
}
|
}
|
||||||
u.consumePendingSharedVaultImport()
|
u.consumePendingSharedVaultImport()
|
||||||
|
u.consumePendingSharedLookup()
|
||||||
u.restoreStartupLifecycleTarget()
|
u.restoreStartupLifecycleTarget()
|
||||||
u.requestMasterPassFocus = u.hasSelectedLifecycleTarget()
|
u.requestMasterPassFocus = u.hasSelectedLifecycleTarget()
|
||||||
u.loadUIPreferences()
|
u.loadUIPreferences()
|
||||||
@@ -785,6 +790,7 @@ func defaultStatePaths(stateDir string) statePaths {
|
|||||||
AutofillCachePath: filepath.Join(baseDir, "autofill-cache.json"),
|
AutofillCachePath: filepath.Join(baseDir, "autofill-cache.json"),
|
||||||
PendingSharedVaultPath: filepath.Join(baseDir, "pending-shared-vault.kdbx"),
|
PendingSharedVaultPath: filepath.Join(baseDir, "pending-shared-vault.kdbx"),
|
||||||
PendingSharedVaultNamePath: filepath.Join(baseDir, "pending-shared-vault-name.txt"),
|
PendingSharedVaultNamePath: filepath.Join(baseDir, "pending-shared-vault-name.txt"),
|
||||||
|
PendingSharedLookupPath: filepath.Join(baseDir, "pending-shared-lookup.txt"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -17,6 +19,8 @@ import (
|
|||||||
"git.julianfamily.org/keepassgo/internal/webdav"
|
"git.julianfamily.org/keepassgo/internal/webdav"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var pendingSharedLookupURLPattern = regexp.MustCompile(`https?://[^\s<>"']+`)
|
||||||
|
|
||||||
func (u *ui) createVaultAction() error {
|
func (u *ui) createVaultAction() error {
|
||||||
key, err := u.currentMasterKey()
|
key, err := u.currentMasterKey()
|
||||||
defer u.clearMasterPassword()
|
defer u.clearMasterPassword()
|
||||||
@@ -78,6 +82,7 @@ func (u *ui) openVaultAction() error {
|
|||||||
u.loadSecuritySettingsFromSession()
|
u.loadSecuritySettingsFromSession()
|
||||||
u.editingEntry = false
|
u.editingEntry = false
|
||||||
u.filter()
|
u.filter()
|
||||||
|
u.applyPendingSharedLookup()
|
||||||
u.applyPendingLifecycleOpenIntent()
|
u.applyPendingLifecycleOpenIntent()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -120,6 +125,7 @@ func (u *ui) startOpenVaultAction() {
|
|||||||
u.loadSecuritySettingsFromSession()
|
u.loadSecuritySettingsFromSession()
|
||||||
u.editingEntry = false
|
u.editingEntry = false
|
||||||
u.filter()
|
u.filter()
|
||||||
|
u.applyPendingSharedLookup()
|
||||||
u.applyPendingLifecycleOpenIntent()
|
u.applyPendingLifecycleOpenIntent()
|
||||||
return nil
|
return nil
|
||||||
}, nil
|
}, nil
|
||||||
@@ -741,6 +747,49 @@ func (u *ui) consumePendingSharedVaultImport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizePendingSharedLookupQuery(raw string) string {
|
||||||
|
value := strings.TrimSpace(raw)
|
||||||
|
if value == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if match := pendingSharedLookupURLPattern.FindString(value); match != "" {
|
||||||
|
value = match
|
||||||
|
}
|
||||||
|
if parsed, err := url.Parse(value); err == nil && strings.TrimSpace(parsed.Hostname()) != "" {
|
||||||
|
return strings.ToLower(strings.TrimSpace(parsed.Hostname()))
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) consumePendingSharedLookup() {
|
||||||
|
path := strings.TrimSpace(u.pendingSharedLookupPath)
|
||||||
|
if path == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
u.state.ErrorMessage = fmt.Sprintf("shared lookup: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = os.Remove(path)
|
||||||
|
u.pendingSharedLookupQuery = normalizePendingSharedLookupQuery(string(data))
|
||||||
|
u.applyPendingSharedLookup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) applyPendingSharedLookup() {
|
||||||
|
query := strings.TrimSpace(u.pendingSharedLookupQuery)
|
||||||
|
status, ok := u.state.Session.(sessionStatus)
|
||||||
|
if query == "" || !ok || !status.HasVault() || status.IsLocked() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.pendingSharedLookupQuery = ""
|
||||||
|
u.state.Section = appstate.SectionEntries
|
||||||
|
u.search.SetText(query)
|
||||||
|
u.filter()
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) importSharedVaultBytesAction(name string, content []byte) error {
|
func (u *ui) importSharedVaultBytesAction(name string, content []byte) error {
|
||||||
target := u.importedVaultDestination(name)
|
target := u.importedVaultDestination(name)
|
||||||
if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil {
|
if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil {
|
||||||
|
|||||||
@@ -8390,6 +8390,9 @@ func TestDefaultStatePathsUsesProvidedStateDir(t *testing.T) {
|
|||||||
if got := paths.PendingSharedVaultNamePath; got != filepath.Join(base, "pending-shared-vault-name.txt") {
|
if got := paths.PendingSharedVaultNamePath; got != filepath.Join(base, "pending-shared-vault-name.txt") {
|
||||||
t.Fatalf("PendingSharedVaultNamePath = %q, want %q", got, filepath.Join(base, "pending-shared-vault-name.txt"))
|
t.Fatalf("PendingSharedVaultNamePath = %q, want %q", got, filepath.Join(base, "pending-shared-vault-name.txt"))
|
||||||
}
|
}
|
||||||
|
if got := paths.PendingSharedLookupPath; got != filepath.Join(base, "pending-shared-lookup.txt") {
|
||||||
|
t.Fatalf("PendingSharedLookupPath = %q, want %q", got, filepath.Join(base, "pending-shared-lookup.txt"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImportedVaultDestinationUsesIncomingFilenameInsideDefaultDirectory(t *testing.T) {
|
func TestImportedVaultDestinationUsesIncomingFilenameInsideDefaultDirectory(t *testing.T) {
|
||||||
@@ -8520,6 +8523,95 @@ func TestUIConsumesPendingSharedVaultImportOnStartup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUIConsumesPendingSharedLookupOnStartupWhenVaultIsAlreadyOpen(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
paths := statePaths{
|
||||||
|
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
|
||||||
|
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
|
||||||
|
RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"),
|
||||||
|
UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"),
|
||||||
|
PendingSharedLookupPath: filepath.Join(dir, "pending-shared-lookup.txt"),
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(paths.PendingSharedLookupPath, []byte("https://bellagio.example.invalid/login\n"), 0o600); err != nil {
|
||||||
|
t.Fatalf("WriteFile(PendingSharedLookupPath) error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u := newUIWithSession("phone", &uiSession{model: vault.Model{
|
||||||
|
Entries: []vault.Entry{
|
||||||
|
{ID: "bellagio-login", Title: "Bellagio", URL: "https://bellagio.example.invalid/login", Path: []string{"Crew", "Internet"}},
|
||||||
|
{ID: "vault-console", Title: "Vault Console", URL: "https://vault.example.invalid", Path: []string{"Crew", "Internet"}},
|
||||||
|
},
|
||||||
|
}}, paths)
|
||||||
|
|
||||||
|
if got := u.search.Text(); got != "bellagio.example.invalid" {
|
||||||
|
t.Fatalf("search after pending shared lookup = %q, want %q", got, "bellagio.example.invalid")
|
||||||
|
}
|
||||||
|
if got := u.filteredTitles(); !slices.Equal(got, []string{"Bellagio"}) {
|
||||||
|
t.Fatalf("filteredTitles() after pending shared lookup = %v, want [Bellagio]", got)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(paths.PendingSharedLookupPath); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Fatalf("Stat(PendingSharedLookupPath) error = %v, want not exist", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizePendingSharedLookupQueryExtractsURLFromTextSnippet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
raw := "Meet the crew at https://bellagio.example.invalid/login before the vault opens."
|
||||||
|
if got := normalizePendingSharedLookupQuery(raw); got != "bellagio.example.invalid" {
|
||||||
|
t.Fatalf("normalizePendingSharedLookupQuery() = %q, want %q", got, "bellagio.example.invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUIAppliesPendingSharedLookupAfterOpeningVault(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
paths := statePaths{
|
||||||
|
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
|
||||||
|
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
|
||||||
|
RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"),
|
||||||
|
UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"),
|
||||||
|
PendingSharedLookupPath: filepath.Join(dir, "pending-shared-lookup.txt"),
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(paths.PendingSharedLookupPath, []byte("https://bellagio.example.invalid/login\n"), 0o600); err != nil {
|
||||||
|
t.Fatalf("WriteFile(PendingSharedLookupPath) error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := vault.MasterKey{Password: "correct horse battery staple"}
|
||||||
|
vaultPath := filepath.Join(dir, "bellagio.kdbx")
|
||||||
|
var encoded bytes.Buffer
|
||||||
|
if err := vault.SaveKDBXWithKey(&encoded, vault.Model{
|
||||||
|
Entries: []vault.Entry{
|
||||||
|
{ID: "bellagio-login", Title: "Bellagio", URL: "https://bellagio.example.invalid/login", Path: []string{"Crew", "Internet"}},
|
||||||
|
{ID: "vault-console", Title: "Vault Console", URL: "https://vault.example.invalid", Path: []string{"Crew", "Internet"}},
|
||||||
|
},
|
||||||
|
}, key); err != nil {
|
||||||
|
t.Fatalf("SaveKDBXWithKey() error = %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(vaultPath, encoded.Bytes(), 0o600); err != nil {
|
||||||
|
t.Fatalf("WriteFile(vaultPath) error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u := newUIWithState("phone", &session.Manager{}, paths)
|
||||||
|
if got := u.search.Text(); got != "" {
|
||||||
|
t.Fatalf("search before open with pending shared lookup = %q, want empty", got)
|
||||||
|
}
|
||||||
|
u.vaultPath.SetText(vaultPath)
|
||||||
|
u.masterPassword.SetText(key.Password)
|
||||||
|
if err := u.openVaultAction(); err != nil {
|
||||||
|
t.Fatalf("openVaultAction() with pending shared lookup error = %v", err)
|
||||||
|
}
|
||||||
|
if got := u.search.Text(); got != "bellagio.example.invalid" {
|
||||||
|
t.Fatalf("search after open with pending shared lookup = %q, want %q", got, "bellagio.example.invalid")
|
||||||
|
}
|
||||||
|
if got := u.filteredTitles(); !slices.Equal(got, []string{"Bellagio"}) {
|
||||||
|
t.Fatalf("filteredTitles() after open with pending shared lookup = %v, want [Bellagio]", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUICurrentShareableVaultPathUsesSelectedVaultPath(t *testing.T) {
|
func TestUICurrentShareableVaultPathUsesSelectedVaultPath(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user