From 2969189d9844fab5ebca1f71419e54c341eafb11 Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 31 Jul 2019 23:17:38 -0400 Subject: [PATCH] Modify cmds that list metadata to not sort; refactor to credentials flags; fix issue with init command --- sp/commands/commands.go | 85 ++++++++++++++++++++++++++++++--------- sp/commands/create.go | 19 ++++----- sp/commands/delete.go | 8 +++- sp/commands/get.go | 8 +++- sp/commands/init.go | 4 ++ sp/commands/list.go | 58 ++++++++++++++++---------- sp/commands/update.go | 14 +++---- sp/repositories/config.go | 4 +- 8 files changed, 135 insertions(+), 65 deletions(-) diff --git a/sp/commands/commands.go b/sp/commands/commands.go index af85553..88a0edd 100644 --- a/sp/commands/commands.go +++ b/sp/commands/commands.go @@ -2,11 +2,12 @@ package commands import ( "context" + "errors" "fmt" "os" - "sort" "time" + "github.com/spf13/cobra" "gopkg.in/AlecAivazis/survey.v1" "github.com/mitchell/selfpass/services/credentials/types" @@ -14,6 +15,40 @@ import ( type CredentialsClientInit func(ctx context.Context) (c types.CredentialsClient) +var errSourceNotFound = errors.New("source host not found") + +type credentialFlagSet struct { + includePasswordFlags bool + includeHostFlag bool + + sourceHost string + noNumbers bool + noSpecials bool + length uint +} + +func (set credentialFlagSet) withPasswordFlags() credentialFlagSet { + set.includePasswordFlags = true + return set +} + +func (set credentialFlagSet) withHostFlag() credentialFlagSet { + set.includeHostFlag = true + return set +} + +func (set *credentialFlagSet) register(cmd *cobra.Command) { + if set.includeHostFlag { + cmd.Flags().StringVarP(&set.sourceHost, "source-host", "s", "", "filter results to this source host") + } + + if set.includePasswordFlags { + cmd.Flags().BoolVarP(&set.noNumbers, "no-numbers", "n", false, "do not use numbers in the generated password") + cmd.Flags().BoolVarP(&set.noSpecials, "no-specials", "p", false, "do not use special characters in the generated password") + cmd.Flags().UintVarP(&set.length, "length", "l", 32, "length of the generated password") + } +} + func check(err error) { if err != nil { fmt.Println(err) @@ -21,18 +56,17 @@ func check(err error) { } } -func selectCredential(client types.CredentialsClient) types.Credential { +func selectCredential(client types.CredentialsClient, sourceHost string) types.Credential { var ( idKey string - source string prompt survey.Prompt ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - mdch, errch := client.GetAllMetadata(ctx, "") - mds := map[string][]types.Metadata{} + mdch, errch := client.GetAllMetadata(ctx, sourceHost) + var mds []types.Metadata fmt.Println() @@ -50,29 +84,42 @@ receive: break receive } - mds[md.SourceHost] = append(mds[md.SourceHost], md) + mds = append(mds, md) } } - sources := []string{} - for source := range mds { - sources = append(sources, source) + var sources []string + mdmap := map[string][]types.Metadata{} + for _, md := range mds { + tmds := mdmap[md.SourceHost] + + if tmds == nil { + mdmap[md.SourceHost] = []types.Metadata{md} + sources = append(sources, md.SourceHost) + continue + } + + mdmap[md.SourceHost] = append(mdmap[md.SourceHost], md) } - sort.Strings(sources) + if sourceHost == "" { + prompt = &survey.Select{ + Message: "Source host:", + Options: sources, + PageSize: 20, + VimMode: true, + } - prompt = &survey.Select{ - Message: "Source host:", - Options: sources, - PageSize: 20, - VimMode: true, + check(survey.AskOne(prompt, &sourceHost, nil)) } - check(survey.AskOne(prompt, &source, nil)) + if len(mdmap[sourceHost]) == 0 { + check(errSourceNotFound) + } keys := []string{} keyIDMap := map[string]string{} - for _, md := range mds[source] { + for _, md := range mdmap[sourceHost] { key := md.Primary if md.Tag != "" { key += "-" + md.Tag @@ -81,8 +128,6 @@ receive: keyIDMap[key] = md.ID } - sort.Strings(keys) - prompt = &survey.Select{ Message: "Primary user key (and tag):", Options: keys, diff --git a/sp/commands/create.go b/sp/commands/create.go index 6a65887..1ecb82c 100644 --- a/sp/commands/create.go +++ b/sp/commands/create.go @@ -18,9 +18,7 @@ import ( ) func makeCreate(repo clitypes.ConfigRepo, initClient CredentialsClientInit) *cobra.Command { - var length uint - var noNumbers bool - var noSpecials bool + flags := credentialFlagSet{}.withPasswordFlags() createCmd := &cobra.Command{ Use: "create", @@ -77,7 +75,7 @@ password.`, check(survey.AskOne(prompt, &newpass, nil)) if newpass { - ci.Password = crypto.GeneratePassword(int(length), !noNumbers, !noSpecials) + ci.Password = crypto.GeneratePassword(int(flags.length), !flags.noNumbers, !flags.noSpecials) var copypass bool prompt = &survey.Confirm{Message: "Copy new pass to clipboard?", Default: true} @@ -130,10 +128,15 @@ password.`, } } - ctx, cancel := context.WithTimeout(context.Background(), time.Second*25) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() - c, err := initClient(ctx).Create(ctx, ci) + client := initClient(ctx) + + ctx, cancel = context.WithTimeout(context.Background(), time.Second*25) + defer cancel() + + c, err := client.Create(ctx, ci) check(err) fmt.Println(c) @@ -147,9 +150,7 @@ password.`, }, } - createCmd.Flags().BoolVarP(&noNumbers, "no-numbers", "n", false, "do not use numbers in the generated password") - createCmd.Flags().BoolVarP(&noSpecials, "no-specials", "s", false, "do not use special characters in the generated password") - createCmd.Flags().UintVarP(&length, "length", "l", 32, "length of the generated password") + flags.register(createCmd) return createCmd } diff --git a/sp/commands/delete.go b/sp/commands/delete.go index 8244c93..d7adc71 100644 --- a/sp/commands/delete.go +++ b/sp/commands/delete.go @@ -10,18 +10,20 @@ import ( ) func makeDelete(initClient CredentialsClientInit) *cobra.Command { + flags := credentialFlagSet{}.withHostFlag() + deleteCmd := &cobra.Command{ Use: "delete", Short: "Delete a credential using the given ID", Long: `Delete a credential using the given ID, permanently. THERE IS NO UNDOING THIS ACTION.`, Run: func(cmd *cobra.Command, args []string) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() client := initClient(ctx) - cred := selectCredential(client) + cred := selectCredential(client, flags.sourceHost) fmt.Println(cred) @@ -38,5 +40,7 @@ func makeDelete(initClient CredentialsClientInit) *cobra.Command { }, } + flags.register(deleteCmd) + return deleteCmd } diff --git a/sp/commands/get.go b/sp/commands/get.go index a1ba957..26db92a 100644 --- a/sp/commands/get.go +++ b/sp/commands/get.go @@ -16,6 +16,8 @@ import ( ) func makeGet(repo clitypes.ConfigRepo, initClient CredentialsClientInit) *cobra.Command { + flags := credentialFlagSet{}.withHostFlag() + getCmd := &cobra.Command{ Use: "get", Short: "Get a credential info and copy password to clipboard", @@ -29,14 +31,14 @@ decrypting password.`, prompt survey.Prompt ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() client := initClient(ctx) masterpass, cfg, err := repo.OpenConfig() check(err) - cred := selectCredential(client) + cred := selectCredential(client, flags.sourceHost) fmt.Println(cred) @@ -90,5 +92,7 @@ decrypting password.`, }, } + flags.register(getCmd) + return getCmd } diff --git a/sp/commands/init.go b/sp/commands/init.go index ac64e2b..bfba8a5 100644 --- a/sp/commands/init.go +++ b/sp/commands/init.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "gopkg.in/AlecAivazis/survey.v1" + "github.com/mitchell/selfpass/sp/repositories" "github.com/mitchell/selfpass/sp/types" ) @@ -31,6 +32,9 @@ the users private key, and server certificates. (All of which will be encrypted) privateKey = strings.Replace(uuid.New().String(), "-", "", -1) ) _, cfg, err := repo.OpenConfig() + if err == repositories.ErrNoConfigFound { + err = nil + } check(err) prompt = &survey.Password{Message: "New master password:"} diff --git a/sp/commands/list.go b/sp/commands/list.go index 0b059ea..744f0f9 100644 --- a/sp/commands/list.go +++ b/sp/commands/list.go @@ -3,7 +3,6 @@ package commands import ( "context" "fmt" - "sort" "time" "github.com/spf13/cobra" @@ -13,7 +12,7 @@ import ( ) func makeList(initClient CredentialsClientInit) *cobra.Command { - var sourceHost string + flags := credentialFlagSet{}.withHostFlag() listCmd := &cobra.Command{ Use: "list", @@ -22,11 +21,16 @@ func makeList(initClient CredentialsClientInit) *cobra.Command { includes almost all the information but the most sensitive.`, Run: func(cmd *cobra.Command, args []string) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() - mdch, errch := initClient(ctx).GetAllMetadata(ctx, sourceHost) - mds := map[string][]types.Metadata{} + client := initClient(ctx) + + ctx, cancel = context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + mdch, errch := client.GetAllMetadata(ctx, flags.sourceHost) + var mds []types.Metadata fmt.Println() @@ -44,32 +48,40 @@ includes almost all the information but the most sensitive.`, break receive } - mds[md.SourceHost] = append(mds[md.SourceHost], md) + mds = append(mds, md) } } - sources := []string{} - for source := range mds { - sources = append(sources, source) + var sources []string + mdmap := map[string][]types.Metadata{} + for _, md := range mds { + tmds := mdmap[md.SourceHost] + + if tmds == nil { + mdmap[md.SourceHost] = []types.Metadata{md} + sources = append(sources, md.SourceHost) + continue + } + + mdmap[md.SourceHost] = append(mdmap[md.SourceHost], md) } - sort.Strings(sources) + if flags.sourceHost == "" { + prompt := &survey.Select{ + Message: "Source host:", + Options: sources, + PageSize: 20, + VimMode: true, + } - prompt := &survey.Select{ - Message: "Source host:", - Options: sources, - PageSize: 20, - VimMode: true, + check(survey.AskOne(prompt, &flags.sourceHost, nil)) } - var source string - check(survey.AskOne(prompt, &source, nil)) + if len(mdmap[flags.sourceHost]) == 0 { + check(errSourceNotFound) + } - sort.Slice(mds[source], func(i, j int) bool { - return mds[source][i].Primary < mds[source][j].Primary - }) - - for _, md := range mds[source] { + for _, md := range mdmap[flags.sourceHost] { fmt.Println(md) } @@ -77,5 +89,7 @@ includes almost all the information but the most sensitive.`, }, } + flags.register(listCmd) + return listCmd } diff --git a/sp/commands/update.go b/sp/commands/update.go index f370e2e..dc69cc7 100644 --- a/sp/commands/update.go +++ b/sp/commands/update.go @@ -18,9 +18,7 @@ import ( ) func makeUpdate(repo clitypes.ConfigRepo, initClient CredentialsClientInit) *cobra.Command { - var length uint - var noNumbers bool - var noSpecials bool + flags := credentialFlagSet{}.withHostFlag().withPasswordFlags() updateCmd := &cobra.Command{ Use: "update", @@ -40,12 +38,12 @@ password.`, masterpass, cfg, err := repo.OpenConfig() check(err) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() client := initClient(ctx) - cred := selectCredential(client) + cred := selectCredential(client, flags.sourceHost) mdqs := []*survey.Question{ { @@ -111,7 +109,7 @@ password.`, check(survey.AskOne(prompt, &randpass, nil)) if randpass { - ci.Password = crypto.GeneratePassword(int(length), !noNumbers, !noSpecials) + ci.Password = crypto.GeneratePassword(int(flags.length), !flags.noNumbers, !flags.noSpecials) var copypass bool prompt = &survey.Confirm{Message: "Copy new pass to clipboard?", Default: true} @@ -182,9 +180,7 @@ password.`, }, } - updateCmd.Flags().BoolVarP(&noNumbers, "no-numbers", "n", false, "do not use numbers in the generated password") - updateCmd.Flags().BoolVarP(&noSpecials, "no-specials", "s", false, "do not use special characters in the generated password") - updateCmd.Flags().UintVarP(&length, "length", "l", 32, "length of the generated password") + flags.register(updateCmd) return updateCmd } diff --git a/sp/repositories/config.go b/sp/repositories/config.go index 1176975..c873925 100644 --- a/sp/repositories/config.go +++ b/sp/repositories/config.go @@ -16,6 +16,8 @@ import ( "github.com/mitchell/selfpass/sp/types" ) +var ErrNoConfigFound = errors.New("no config found, run 'init' command") + func NewConfigManager(cfgFile *string) *ConfigManager { return &ConfigManager{ cfgFile: cfgFile, @@ -57,7 +59,7 @@ func (mgr *ConfigManager) OpenConfig() (output string, v *viper.Viper, err error var configDecrypted bool if _, err := os.Open(cfg); os.IsNotExist(err) { - return output, mgr.v, fmt.Errorf("no config found, run 'init' command") + return output, mgr.v, ErrNoConfigFound } prompt := &survey.Password{Message: "Master password:"}