Skip to content

Commit

Permalink
Merge pull request #118 from msladek/and-filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
hazcod authored Jul 18, 2022
2 parents 3f8f037 + ac6f5a7 commit 20b808f
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 39 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Flags
| `-log=LEVEL` | The log level from debug (5) to error (1) |
| `-nonInteractive` | Disable prompts and fail instead |
| `-pin` | Enable Quick Unlock using a PIN |
| `-and` | Combines filters with AND instead of default OR |
| `-sort` | Sort the output by title and username of the `list` and `show` command |
| `-trashed` | Show trashed items in the `list` and `show` command |
| `-clipboardPrimary` | Use primary X selection instead of clipboard for the `copy` command |
Expand Down
3 changes: 3 additions & 0 deletions cmd/enpasscli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Args struct {
pinEnable *bool
sort *bool
trashed *bool
and *bool
clipboardPrimary *bool
}

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

var store *unlock.SecureStore
if !*args.pinEnable {
Expand Down
85 changes: 49 additions & 36 deletions pkg/enpass/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ type Vault struct {
// Logger : the logger instance
logger logrus.Logger

// settings for filtering entries
FilterFields []string
FilterAnd bool

// vault.enpassdb : SQLCipher database
databaseFilename string

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

// NewVault : Create new instance of vault and load vault info
func NewVault(vaultPath string, logLevel logrus.Level) (*Vault, error) {
v := Vault{logger: *logrus.New()}
v := Vault{
logger: *logrus.New(),
FilterFields: []string{"title", "subtitle"},
}
v.logger.SetLevel(logLevel)

if vaultPath == "" {
Expand Down Expand Up @@ -185,20 +192,13 @@ func (v *Vault) Close() {
}
}

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

rows, err := v.db.Query(`
SELECT uuid, type, created_at, field_updated_at, title,
subtitle, note, trashed, item.deleted, category,
label, value, key, last_used, sensitive, item.icon
FROM item
INNER JOIN itemfield ON uuid = item_uuid
`)

rows, err := v.executeEntryQuery(cardType, filters)
if err != nil {
return nil, errors.Wrap(err, "could not retrieve cards from database")
}
Expand All @@ -219,32 +219,6 @@ func (v *Vault) GetEntries(cardType string, filters []string) ([]Card, error) {

card.RawValue = card.value

// if item has been deleted
if card.IsDeleted() {
continue
}

// if we specify a type filter
if cardType != "" && card.Type != cardType {
continue
}

// check any supplied title filters
if len(filters) > 0 {
found := false

for _, filter := range filters {
if strings.Contains(strings.ToLower(card.Title), strings.ToLower(filter)) {
found = true
break
}
}

if !found {
continue
}
}

cards = append(cards, card)
}

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

return ret, nil
}

func (v *Vault) executeEntryQuery(cardType string, filters []string) (*sql.Rows, error) {
query := `
SELECT uuid, type, created_at, field_updated_at, title,
subtitle, note, trashed, item.deleted, category,
label, value, key, last_used, sensitive, item.icon
FROM item
INNER JOIN itemfield ON uuid = item_uuid
`

where := []string{"item.deleted = ?"}
values := []interface{}{0}

if cardType != "" {
where = append(where, "type = ?")
values = append(values, cardType)
}

filterWhere := []string{}
for _, filter := range filters {
fq := "(0"
for _, field := range v.FilterFields {
fq += " + instr(lower(" + field + "), ?)"
values = append(values, strings.ToLower(filter))
}
fq += " > 0)"
filterWhere = append(filterWhere, fq)
}

if v.FilterAnd {
where = append(where, filterWhere...)
} else if len(filterWhere) > 0 {
where = append(where, "("+strings.Join(filterWhere, " OR ")+")")
}

query += " WHERE " + strings.Join(where, " AND ")
v.logger.Trace("query: ", query)
return v.db.Query(query, values...)
}
79 changes: 76 additions & 3 deletions pkg/enpass/vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,85 @@ func TestVault_GetEntries(t *testing.T) {
t.Errorf("opening vault failed: %+v", err)
}

entries, err := vault.GetEntries("password", nil)
Assert_GetEntries(t, vault, nil, 1)
}

func TestVault_GetEntries_Filter_OR(t *testing.T) {
vault, err := NewVault(vaultPath, logrus.ErrorLevel)
if err != nil {
t.Errorf("vault get entries failed: %+v", err)
t.Errorf("vault initialization failed: %+v", err)
}
defer vault.Close()
credentials := &VaultCredentials{Password: testPassword}
if err := vault.Open(credentials); err != nil {
t.Errorf("opening vault failed: %+v", err)
}

vault.FilterAnd = false

if len(entries) != 1 {
Assert_GetEntries(t, vault, []string{"mylogin"}, 1) // matches title
Assert_GetEntries(t, vault, []string{"myusername"}, 1) // matches subtitle
Assert_GetEntries(t, vault, []string{"inexistent"}, 0) // matches nothing
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)
Assert_GetEntries(t, vault, []string{"mylogin", "inexistent"}, 1)
Assert_GetEntries(t, vault, []string{"inexistent", "alsoinexistent"}, 0)
}

func TestVault_GetEntries_Filter_AND(t *testing.T) {
vault, err := NewVault(vaultPath, logrus.ErrorLevel)
if err != nil {
t.Errorf("vault initialization failed: %+v", err)
}
defer vault.Close()
credentials := &VaultCredentials{Password: testPassword}
if err := vault.Open(credentials); err != nil {
t.Errorf("opening vault failed: %+v", err)
}

vault.FilterAnd = true

Assert_GetEntries(t, vault, []string{"mylogin"}, 1) // matches title
Assert_GetEntries(t, vault, []string{"myusername"}, 1) // matches subtitle
Assert_GetEntries(t, vault, []string{"inexistent"}, 0) // matches nothing
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)
Assert_GetEntries(t, vault, []string{"mylogin", "inexistent"}, 0)
Assert_GetEntries(t, vault, []string{"inexistent", "alsoinexistent"}, 0)
}

func TestVault_GetEntries_Filter_Fields(t *testing.T) {
vault, err := NewVault(vaultPath, logrus.ErrorLevel)
if err != nil {
t.Errorf("vault initialization failed: %+v", err)
}
defer vault.Close()
credentials := &VaultCredentials{Password: testPassword}
if err := vault.Open(credentials); err != nil {
t.Errorf("opening vault failed: %+v", err)
}

vault.FilterAnd = false

vault.FilterFields = []string{"title"}
Assert_GetEntries(t, vault, []string{"mylogin"}, 1) // matches title
Assert_GetEntries(t, vault, []string{"myusername"}, 0) // matches subtitle
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)

vault.FilterFields = []string{"subtitle"}
Assert_GetEntries(t, vault, []string{"mylogin"}, 0) // matches title
Assert_GetEntries(t, vault, []string{"myusername"}, 1) // matches subtitle
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)

vault.FilterFields = []string{"title", "subtitle"}
Assert_GetEntries(t, vault, []string{"mylogin"}, 1) // matches title
Assert_GetEntries(t, vault, []string{"myusername"}, 1) // matches subtitle
Assert_GetEntries(t, vault, []string{"mylogin", "myusername"}, 1)
}

func Assert_GetEntries(t *testing.T, vault *Vault, filters []string, expectedCount int) {
entries, err := vault.GetEntries("password", filters)
if err != nil {
t.Errorf("vault get entries failed: %+v", err)
} else if len(entries) != expectedCount {
t.Error("wrong number of entries returned")
}
}

0 comments on commit 20b808f

Please sign in to comment.