Skip to content

Commit 20b808f

Browse files
authored
Merge pull request #118 from msladek/and-filtering
2 parents 3f8f037 + ac6f5a7 commit 20b808f

File tree

4 files changed

+129
-39
lines changed

4 files changed

+129
-39
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Flags
4848
| `-log=LEVEL` | The log level from debug (5) to error (1) |
4949
| `-nonInteractive` | Disable prompts and fail instead |
5050
| `-pin` | Enable Quick Unlock using a PIN |
51+
| `-and` | Combines filters with AND instead of default OR |
5152
| `-sort` | Sort the output by title and username of the `list` and `show` command |
5253
| `-trashed` | Show trashed items in the `list` and `show` command |
5354
| `-clipboardPrimary` | Use primary X selection instead of clipboard for the `copy` command |

cmd/enpasscli/main.go

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type Args struct {
5353
pinEnable *bool
5454
sort *bool
5555
trashed *bool
56+
and *bool
5657
clipboardPrimary *bool
5758
}
5859

@@ -63,6 +64,7 @@ func (args *Args) parse() {
6364
args.logLevelStr = flag.String("log", defaultLogLevel.String(), "The log level from debug (5) to error (1).")
6465
args.nonInteractive = flag.Bool("nonInteractive", false, "Disable prompts and fail instead.")
6566
args.pinEnable = flag.Bool("pin", false, "Enable PIN.")
67+
args.and = flag.Bool("and", false, "Combines filters with AND instead of default OR.")
6668
args.sort = flag.Bool("sort", false, "Sort the output by title and username of the 'list' and 'show' command.")
6769
args.trashed = flag.Bool("trashed", false, "Show trashed items in the 'list' and 'show' command.")
6870
args.clipboardPrimary = flag.Bool("clipboardPrimary", false, "Use primary X selection instead of clipboard for the 'copy' command.")
@@ -278,6 +280,7 @@ func main() {
278280
if err != nil {
279281
logger.WithError(err).Fatal("could not create vault")
280282
}
283+
vault.FilterAnd = *args.and
281284

282285
var store *unlock.SecureStore
283286
if !*args.pinEnable {

pkg/enpass/vault.go

+49-36
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ type Vault struct {
2626
// Logger : the logger instance
2727
logger logrus.Logger
2828

29+
// settings for filtering entries
30+
FilterFields []string
31+
FilterAnd bool
32+
2933
// vault.enpassdb : SQLCipher database
3034
databaseFilename string
3135

@@ -54,7 +58,10 @@ func (credentials *VaultCredentials) IsComplete() bool {
5458

5559
// NewVault : Create new instance of vault and load vault info
5660
func NewVault(vaultPath string, logLevel logrus.Level) (*Vault, error) {
57-
v := Vault{logger: *logrus.New()}
61+
v := Vault{
62+
logger: *logrus.New(),
63+
FilterFields: []string{"title", "subtitle"},
64+
}
5865
v.logger.SetLevel(logLevel)
5966

6067
if vaultPath == "" {
@@ -185,20 +192,13 @@ func (v *Vault) Close() {
185192
}
186193
}
187194

188-
// GetEntries : return the password entries in the Enpass database.
195+
// GetEntries : return the cardType entries in the Enpass database filtered by filters.
189196
func (v *Vault) GetEntries(cardType string, filters []string) ([]Card, error) {
190197
if v.db == nil || v.vaultInfo.VaultName == "" {
191198
return nil, errors.New("vault is not initialized")
192199
}
193200

194-
rows, err := v.db.Query(`
195-
SELECT uuid, type, created_at, field_updated_at, title,
196-
subtitle, note, trashed, item.deleted, category,
197-
label, value, key, last_used, sensitive, item.icon
198-
FROM item
199-
INNER JOIN itemfield ON uuid = item_uuid
200-
`)
201-
201+
rows, err := v.executeEntryQuery(cardType, filters)
202202
if err != nil {
203203
return nil, errors.Wrap(err, "could not retrieve cards from database")
204204
}
@@ -219,32 +219,6 @@ func (v *Vault) GetEntries(cardType string, filters []string) ([]Card, error) {
219219

220220
card.RawValue = card.value
221221

222-
// if item has been deleted
223-
if card.IsDeleted() {
224-
continue
225-
}
226-
227-
// if we specify a type filter
228-
if cardType != "" && card.Type != cardType {
229-
continue
230-
}
231-
232-
// check any supplied title filters
233-
if len(filters) > 0 {
234-
found := false
235-
236-
for _, filter := range filters {
237-
if strings.Contains(strings.ToLower(card.Title), strings.ToLower(filter)) {
238-
found = true
239-
break
240-
}
241-
}
242-
243-
if !found {
244-
continue
245-
}
246-
}
247-
248222
cards = append(cards, card)
249223
}
250224

@@ -276,3 +250,42 @@ func (v *Vault) GetEntry(cardType string, filters []string, unique bool) (*Card,
276250

277251
return ret, nil
278252
}
253+
254+
func (v *Vault) executeEntryQuery(cardType string, filters []string) (*sql.Rows, error) {
255+
query := `
256+
SELECT uuid, type, created_at, field_updated_at, title,
257+
subtitle, note, trashed, item.deleted, category,
258+
label, value, key, last_used, sensitive, item.icon
259+
FROM item
260+
INNER JOIN itemfield ON uuid = item_uuid
261+
`
262+
263+
where := []string{"item.deleted = ?"}
264+
values := []interface{}{0}
265+
266+
if cardType != "" {
267+
where = append(where, "type = ?")
268+
values = append(values, cardType)
269+
}
270+
271+
filterWhere := []string{}
272+
for _, filter := range filters {
273+
fq := "(0"
274+
for _, field := range v.FilterFields {
275+
fq += " + instr(lower(" + field + "), ?)"
276+
values = append(values, strings.ToLower(filter))
277+
}
278+
fq += " > 0)"
279+
filterWhere = append(filterWhere, fq)
280+
}
281+
282+
if v.FilterAnd {
283+
where = append(where, filterWhere...)
284+
} else if len(filterWhere) > 0 {
285+
where = append(where, "("+strings.Join(filterWhere, " OR ")+")")
286+
}
287+
288+
query += " WHERE " + strings.Join(where, " AND ")
289+
v.logger.Trace("query: ", query)
290+
return v.db.Query(query, values...)
291+
}

pkg/enpass/vault_test.go

+76-3
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,85 @@ func TestVault_GetEntries(t *testing.T) {
3434
t.Errorf("opening vault failed: %+v", err)
3535
}
3636

37-
entries, err := vault.GetEntries("password", nil)
37+
Assert_GetEntries(t, vault, nil, 1)
38+
}
39+
40+
func TestVault_GetEntries_Filter_OR(t *testing.T) {
41+
vault, err := NewVault(vaultPath, logrus.ErrorLevel)
3842
if err != nil {
39-
t.Errorf("vault get entries failed: %+v", err)
43+
t.Errorf("vault initialization failed: %+v", err)
4044
}
45+
defer vault.Close()
46+
credentials := &VaultCredentials{Password: testPassword}
47+
if err := vault.Open(credentials); err != nil {
48+
t.Errorf("opening vault failed: %+v", err)
49+
}
50+
51+
vault.FilterAnd = false
4152

42-
if len(entries) != 1 {
53+
Assert_GetEntries(t, vault, []string{"mylogin"}, 1) // matches title
54+
Assert_GetEntries(t, vault, []string{"myusername"}, 1) // matches subtitle
55+
Assert_GetEntries(t, vault, []string{"inexistent"}, 0) // matches nothing
56+
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)
57+
Assert_GetEntries(t, vault, []string{"mylogin", "inexistent"}, 1)
58+
Assert_GetEntries(t, vault, []string{"inexistent", "alsoinexistent"}, 0)
59+
}
60+
61+
func TestVault_GetEntries_Filter_AND(t *testing.T) {
62+
vault, err := NewVault(vaultPath, logrus.ErrorLevel)
63+
if err != nil {
64+
t.Errorf("vault initialization failed: %+v", err)
65+
}
66+
defer vault.Close()
67+
credentials := &VaultCredentials{Password: testPassword}
68+
if err := vault.Open(credentials); err != nil {
69+
t.Errorf("opening vault failed: %+v", err)
70+
}
71+
72+
vault.FilterAnd = true
73+
74+
Assert_GetEntries(t, vault, []string{"mylogin"}, 1) // matches title
75+
Assert_GetEntries(t, vault, []string{"myusername"}, 1) // matches subtitle
76+
Assert_GetEntries(t, vault, []string{"inexistent"}, 0) // matches nothing
77+
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)
78+
Assert_GetEntries(t, vault, []string{"mylogin", "inexistent"}, 0)
79+
Assert_GetEntries(t, vault, []string{"inexistent", "alsoinexistent"}, 0)
80+
}
81+
82+
func TestVault_GetEntries_Filter_Fields(t *testing.T) {
83+
vault, err := NewVault(vaultPath, logrus.ErrorLevel)
84+
if err != nil {
85+
t.Errorf("vault initialization failed: %+v", err)
86+
}
87+
defer vault.Close()
88+
credentials := &VaultCredentials{Password: testPassword}
89+
if err := vault.Open(credentials); err != nil {
90+
t.Errorf("opening vault failed: %+v", err)
91+
}
92+
93+
vault.FilterAnd = false
94+
95+
vault.FilterFields = []string{"title"}
96+
Assert_GetEntries(t, vault, []string{"mylogin"}, 1) // matches title
97+
Assert_GetEntries(t, vault, []string{"myusername"}, 0) // matches subtitle
98+
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)
99+
100+
vault.FilterFields = []string{"subtitle"}
101+
Assert_GetEntries(t, vault, []string{"mylogin"}, 0) // matches title
102+
Assert_GetEntries(t, vault, []string{"myusername"}, 1) // matches subtitle
103+
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)
104+
105+
vault.FilterFields = []string{"title", "subtitle"}
106+
Assert_GetEntries(t, vault, []string{"mylogin"}, 1) // matches title
107+
Assert_GetEntries(t, vault, []string{"myusername"}, 1) // matches subtitle
108+
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)
109+
}
110+
111+
func Assert_GetEntries(t *testing.T, vault *Vault, filters []string, expectedCount int) {
112+
entries, err := vault.GetEntries("password", filters)
113+
if err != nil {
114+
t.Errorf("vault get entries failed: %+v", err)
115+
} else if len(entries) != expectedCount {
43116
t.Error("wrong number of entries returned")
44117
}
45118
}

0 commit comments

Comments
 (0)