1package commands
  2
  3import (
  4	"bufio"
  5	"fmt"
  6	"os"
  7	"strings"
  8
  9	"github.com/mattn/go-isatty"
 10	"github.com/pkg/errors"
 11	"github.com/spf13/cobra"
 12
 13	"github.com/MichaelMure/git-bug/bridge"
 14	"github.com/MichaelMure/git-bug/bridge/core"
 15	"github.com/MichaelMure/git-bug/bridge/core/auth"
 16	"github.com/MichaelMure/git-bug/cache"
 17)
 18
 19type bridgeAuthAddTokenOptions struct {
 20	target string
 21	login  string
 22	user   string
 23}
 24
 25func newBridgeAuthAddTokenCommand() *cobra.Command {
 26	env := newEnv()
 27	options := bridgeAuthAddTokenOptions{}
 28
 29	cmd := &cobra.Command{
 30		Use:      "add-token [TOKEN]",
 31		Short:    "Store a new token",
 32		PreRunE:  loadBackendEnsureUser(env),
 33		PostRunE: closeBackend(env),
 34		RunE: func(cmd *cobra.Command, args []string) error {
 35			return runBridgeAuthAddToken(env, options, args)
 36		},
 37		Args: cobra.MaximumNArgs(1),
 38	}
 39
 40	flags := cmd.Flags()
 41	flags.SortFlags = false
 42
 43	flags.StringVarP(&options.target, "target", "t", "",
 44		fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
 45	flags.StringVarP(&options.login,
 46		"login", "l", "", "The login in the remote bug-tracker")
 47	flags.StringVarP(&options.user,
 48		"user", "u", "", "The user to add the token to. Default is the current user")
 49
 50	return cmd
 51}
 52
 53func runBridgeAuthAddToken(env *Env, opts bridgeAuthAddTokenOptions, args []string) error {
 54	// Note: as bridgeAuthAddTokenLogin is not checked against the remote bug-tracker,
 55	// it's possible to register a credential with an incorrect login (including bad case).
 56	// The consequence is that it will not get picked later by the bridge. I find that
 57	// checking it would require a cumbersome UX (need to provide a base URL for some bridges, ...)
 58	// so it's probably not worth it, unless we refactor that entirely.
 59
 60	if opts.target == "" {
 61		return fmt.Errorf("flag --target is required")
 62	}
 63	if opts.login == "" {
 64		return fmt.Errorf("flag --login is required")
 65	}
 66
 67	if !core.TargetExist(opts.target) {
 68		return fmt.Errorf("unknown target")
 69	}
 70
 71	var value string
 72
 73	if len(args) == 1 {
 74		value = args[0]
 75	} else {
 76		// Read from Stdin
 77		if isatty.IsTerminal(os.Stdin.Fd()) {
 78			env.err.Println("Enter the token:")
 79		}
 80		reader := bufio.NewReader(os.Stdin)
 81		raw, err := reader.ReadString('\n')
 82		if err != nil {
 83			return fmt.Errorf("reading from stdin: %v", err)
 84		}
 85		value = strings.TrimSuffix(raw, "\n")
 86	}
 87
 88	var user *cache.IdentityCache
 89	var err error
 90
 91	if opts.user == "" {
 92		user, err = env.backend.GetUserIdentity()
 93	} else {
 94		user, err = env.backend.ResolveIdentityPrefix(opts.user)
 95	}
 96	if err != nil {
 97		return err
 98	}
 99
100	metaKey, _ := bridge.LoginMetaKey(opts.target)
101	login, ok := user.ImmutableMetadata()[metaKey]
102
103	switch {
104	case ok && login == opts.login:
105		// nothing to do
106	case ok && login != opts.login:
107		return fmt.Errorf("this user is already tagged with a different %s login", opts.target)
108	default:
109		user.SetMetadata(metaKey, opts.login)
110		err = user.Commit()
111		if err != nil {
112			return err
113		}
114	}
115
116	token := auth.NewToken(opts.target, value)
117	token.SetMetadata(auth.MetaKeyLogin, opts.login)
118
119	if err := token.Validate(); err != nil {
120		return errors.Wrap(err, "invalid token")
121	}
122
123	err = auth.Store(env.repo, token)
124	if err != nil {
125		return err
126	}
127
128	env.out.Printf("token %s added\n", token.ID())
129	return nil
130}