mirror of
https://github.com/gopasspw/gopass.git
synced 2025-12-08 19:24:54 +00:00
this fix will skip confirmation input when using gopass recipient add --force fixes #3172 Signed-off-by: Tommi2Day <tommi2day@tommi2day.net> Co-authored-by: Tommi2Day <tommi2day@tommi2day.net>
311 lines
8.7 KiB
Go
311 lines
8.7 KiB
Go
package action
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/gopasspw/gopass/internal/action/exit"
|
|
"github.com/gopasspw/gopass/internal/config"
|
|
"github.com/gopasspw/gopass/internal/cui"
|
|
"github.com/gopasspw/gopass/internal/out"
|
|
"github.com/gopasspw/gopass/internal/tree"
|
|
"github.com/gopasspw/gopass/pkg/ctxutil"
|
|
"github.com/gopasspw/gopass/pkg/debug"
|
|
"github.com/gopasspw/gopass/pkg/set"
|
|
"github.com/gopasspw/gopass/pkg/termio"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
var removalWarning = `
|
|
|
|
|
|
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
@ WARNING: REMOVING A USER WILL NOT REVOKE ACCESS FROM OLD REVISONS! @
|
|
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
THE USER %s WILL STILL BE ABLE TO ACCESS ANY OLD COPY OF THE STORE AND
|
|
ANY OLD REVISION THEY HAD ACCESS TO.
|
|
|
|
ANY CREDENTIALS THIS USER HAD ACCESS TO NEED TO BE CONSIDERED COMPROMISED
|
|
AND SHOULD BE REVOKED.
|
|
|
|
This feature is only meant for revoking access to any added or changed
|
|
credentials.
|
|
|
|
`
|
|
|
|
// RecipientsPrint prints all recipients per store.
|
|
func (s *Action) RecipientsPrint(c *cli.Context) error {
|
|
ctx := ctxutil.WithGlobalFlags(c)
|
|
out.Printf(ctx, "Hint: run 'gopass sync' to import any missing public keys")
|
|
|
|
t, err := s.Store.RecipientsTree(ctx, c.Bool("pretty"))
|
|
if err != nil {
|
|
return exit.Error(exit.List, err, "failed to list recipients: %s", err)
|
|
}
|
|
|
|
fmt.Fprintln(stdout, t.Format(tree.INF))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Action) recipientsList(ctx context.Context) []string {
|
|
t, err := s.Store.RecipientsTree(ctxutil.WithHidden(ctx, true), false)
|
|
if err != nil {
|
|
debug.Log("failed to list recipients: %s", err)
|
|
|
|
return nil
|
|
}
|
|
|
|
return t.List(tree.INF)
|
|
}
|
|
|
|
// RecipientsComplete will print a list of recipients for bash
|
|
// completion.
|
|
func (s *Action) RecipientsComplete(c *cli.Context) {
|
|
ctx := ctxutil.WithGlobalFlags(c)
|
|
if err := s.IsInitialized(c); err != nil {
|
|
debug.Log("IsInitialized returned error: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
for _, v := range s.recipientsList(ctx) {
|
|
fmt.Fprintln(stdout, v)
|
|
}
|
|
}
|
|
|
|
// RecipientsAck updates `recipients.hash`.
|
|
func (s *Action) RecipientsAck(c *cli.Context) error {
|
|
ctx := ctxutil.WithGlobalFlags(c)
|
|
|
|
return s.Store.SaveRecipients(ctxutil.WithHidden(ctx, true), true)
|
|
}
|
|
|
|
// RecipientsAdd adds new recipients.
|
|
func (s *Action) RecipientsAdd(c *cli.Context) error {
|
|
ctx := ctxutil.WithGlobalFlags(c)
|
|
store := c.String("store")
|
|
force := c.Bool("force")
|
|
added := 0
|
|
|
|
// select store.
|
|
if store == "" {
|
|
store = cui.AskForStore(ctx, s.Store)
|
|
}
|
|
|
|
if err := s.Store.CheckRecipients(ctx, store); err != nil && !force {
|
|
out.Errorf(ctx, "%s. Please remove expired keys or extend their validity. See https://go.gopass.pw/faq#expired-recipients", err.Error())
|
|
|
|
return exit.Error(exit.Recipients, err, "recipients invalid: %q", err)
|
|
}
|
|
|
|
crypto := s.Store.Crypto(ctx, store)
|
|
|
|
// select recipient.
|
|
recipients := c.Args().Slice()
|
|
if len(recipients) < 1 {
|
|
out.Notice(ctx, "Fetching available recipients. Please wait...")
|
|
|
|
debug.Log("no recipients given, asking for selection")
|
|
r, err := s.recipientsSelectForAdd(ctx, store)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
recipients = r
|
|
}
|
|
|
|
debug.Log("adding recipients: %+v", recipients)
|
|
for _, r := range recipients {
|
|
keys, err := crypto.FindRecipients(ctx, r)
|
|
if err != nil {
|
|
out.Warningf(ctx, "Failed to list public key %q: %s", r, err)
|
|
var imported bool
|
|
if sub, err := s.Store.GetSubStore(store); err == nil {
|
|
ctx = config.WithMount(ctx, store)
|
|
if err := sub.ImportMissingPublicKeys(ctx, r); err != nil {
|
|
out.Warningf(ctx, "Failed to import missing public keys: %s", err)
|
|
}
|
|
imported = err == nil
|
|
}
|
|
if !force && !imported {
|
|
continue
|
|
}
|
|
keys = []string{r}
|
|
}
|
|
if len(keys) < 1 && !force && crypto.Name() == "gpgcli" {
|
|
out.Printf(ctx, "Warning: No matching valid key found. If the key is in your keyring you may need to validate it.")
|
|
out.Printf(ctx, "If this is your key: gpg --edit-key %s; trust (set to ultimate); quit", r)
|
|
out.Printf(ctx, "If this is not your key: gpg --edit-key %s; lsign; trust; save; quit", r)
|
|
out.Printf(ctx, "You may need to run 'gpg --update-trustdb' afterwards")
|
|
|
|
continue
|
|
}
|
|
|
|
debug.Log("found recipients for %q: %+v", r, keys)
|
|
|
|
if !force && !termio.AskForConfirmation(ctx, fmt.Sprintf("Do you want to add %q (key %q) as a recipient to the store %q?", crypto.FormatKey(ctx, r, ""), r, store)) {
|
|
continue
|
|
}
|
|
|
|
if err := s.Store.AddRecipient(ctx, store, r); err != nil {
|
|
return exit.Error(exit.Recipients, err, "failed to add recipient %q: %s", r, err)
|
|
}
|
|
added++
|
|
}
|
|
if added < 1 {
|
|
return exit.Error(exit.Unknown, nil, "no key added")
|
|
}
|
|
|
|
out.Printf(ctx, "\nAdded %d recipients", added)
|
|
out.Printf(ctx, "You need to run 'gopass sync' to push these changes")
|
|
|
|
return nil
|
|
}
|
|
|
|
// RecipientsRemove removes recipients.
|
|
func (s *Action) RecipientsRemove(c *cli.Context) error {
|
|
ctx := ctxutil.WithGlobalFlags(c)
|
|
store := c.String("store")
|
|
force := c.Bool("force")
|
|
removed := 0
|
|
|
|
// select store if none is given.
|
|
if !c.IsSet("store") {
|
|
store = cui.AskForStore(ctx, s.Store)
|
|
}
|
|
|
|
// get the crypto backend from the store so we can perform the correct
|
|
// recipient checks.
|
|
crypto := s.Store.Crypto(ctx, store)
|
|
|
|
// ask to select a recipient if none are given.
|
|
recipients := c.Args().Slice()
|
|
if len(recipients) < 1 {
|
|
rs, err := s.recipientsSelectForRemoval(ctx, store)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
recipients = rs
|
|
}
|
|
|
|
knownRecipients := s.Store.ListRecipients(ctx, store)
|
|
|
|
// try to remove all given recipients.
|
|
for _, r := range recipients {
|
|
// check and warn if the user is trying to remove themselves
|
|
kl, err := crypto.FindIdentities(ctx, r)
|
|
if err == nil && len(kl) > 0 {
|
|
if !termio.AskForConfirmation(ctx, fmt.Sprintf("Do you want to remove yourself (%s) from the recipients?", r)) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// if a literal recipient (e.g. ID) is given just remove that w/o any kind of lookups.
|
|
if set.Contains(knownRecipients, r) {
|
|
debug.Log("Removing %q from %q (direct)", r, store)
|
|
if err := s.Store.RemoveRecipient(ctx, store, r); err != nil {
|
|
return exit.Error(exit.Recipients, err, "failed to remove recipient %q: %s", r, err)
|
|
}
|
|
|
|
removed++
|
|
|
|
continue
|
|
}
|
|
|
|
// look up the full key ID of the given recipient (could be email or any kind of short ID)
|
|
keys, err := crypto.FindRecipients(ctx, r)
|
|
if err != nil {
|
|
out.Printf(ctx, "WARNING: Failed to list public key %q: %s", r, err)
|
|
out.Printf(ctx, "Hint: You can use `--force` to remove unknown keys.")
|
|
if !force {
|
|
continue
|
|
}
|
|
|
|
keys = []string{r}
|
|
}
|
|
debug.Log("FindRecipients translated %q into %v", r, keys)
|
|
|
|
if len(keys) < 1 && !force {
|
|
out.Printf(ctx, "Warning: No matching valid key found. If the key is in your keyring you may need to validate it.")
|
|
out.Printf(ctx, "If this is your key: gpg --edit-key %s; trust (set to ultimate); quit", r)
|
|
out.Printf(ctx, "If this is not your key: gpg --edit-key %s; lsign; trust; save; quit", r)
|
|
out.Printf(ctx, "You may need to run 'gpg --update-trustdb' afterwards")
|
|
|
|
continue
|
|
}
|
|
|
|
recp := r //nolint:copyloopvar
|
|
if len(keys) > 0 {
|
|
if nr := crypto.Fingerprint(ctx, keys[0]); nr != "" {
|
|
debug.Log("Fingerprint translated %q into %q", keys[0], nr)
|
|
recp = nr
|
|
}
|
|
}
|
|
|
|
debug.Log("Removing %q from %q (indirect)", recp, store)
|
|
if err := s.Store.RemoveRecipient(ctx, store, recp); err != nil {
|
|
return exit.Error(exit.Recipients, err, "failed to remove recipient %q: %s", recp, err)
|
|
}
|
|
|
|
fmt.Fprintf(stdout, removalWarning, r)
|
|
removed++
|
|
}
|
|
|
|
if removed < 1 {
|
|
return exit.Error(exit.Unknown, nil, "no key removed")
|
|
}
|
|
|
|
out.Printf(ctx, "\nRemoved %d recipients", removed)
|
|
out.Printf(ctx, "You need to run 'gopass sync' to push these changes")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Action) recipientsSelectForRemoval(ctx context.Context, store string) ([]string, error) {
|
|
crypto := s.Store.Crypto(ctx, store)
|
|
|
|
ids := s.Store.ListRecipients(ctx, store)
|
|
choices := make([]string, 0, len(ids))
|
|
for _, id := range ids {
|
|
choices = append(choices, crypto.FormatKey(ctx, id, ""))
|
|
}
|
|
|
|
if len(choices) < 1 {
|
|
return nil, nil
|
|
}
|
|
|
|
act, sel := cui.GetSelection(ctx, "Remove recipient -", choices)
|
|
switch act {
|
|
case "default":
|
|
fallthrough
|
|
case "show":
|
|
return []string{ids[sel]}, nil
|
|
default:
|
|
return nil, exit.Error(exit.Aborted, nil, "user aborted")
|
|
}
|
|
}
|
|
|
|
func (s *Action) recipientsSelectForAdd(ctx context.Context, store string) ([]string, error) {
|
|
crypto := s.Store.Crypto(ctx, store)
|
|
|
|
choices := []string{}
|
|
kl, _ := crypto.FindRecipients(ctx)
|
|
for _, key := range kl {
|
|
choices = append(choices, crypto.FormatKey(ctx, key, ""))
|
|
}
|
|
|
|
if len(choices) < 1 {
|
|
return nil, nil
|
|
}
|
|
|
|
act, sel := cui.GetSelection(ctx, "Add Recipient -", choices)
|
|
switch act {
|
|
case "default":
|
|
fallthrough
|
|
case "show":
|
|
return []string{kl[sel]}, nil
|
|
default:
|
|
return nil, exit.Error(exit.Aborted, nil, "user aborted")
|
|
}
|
|
}
|