1package login
2
3import (
4	"bufio"
5	"fmt"
6	"net/url"
7	"strings"
8
9	"github.com/cli/cli/v2/pkg/cmdutil"
10	"github.com/cli/cli/v2/pkg/iostreams"
11	"github.com/spf13/cobra"
12)
13
14const tokenUser = "x-access-token"
15
16type config interface {
17	GetWithSource(string, string) (string, string, error)
18}
19
20type CredentialOptions struct {
21	IO     *iostreams.IOStreams
22	Config func() (config, error)
23
24	Operation string
25}
26
27func NewCmdCredential(f *cmdutil.Factory, runF func(*CredentialOptions) error) *cobra.Command {
28	opts := &CredentialOptions{
29		IO: f.IOStreams,
30		Config: func() (config, error) {
31			return f.Config()
32		},
33	}
34
35	cmd := &cobra.Command{
36		Use:    "git-credential",
37		Args:   cobra.ExactArgs(1),
38		Short:  "Implements git credential helper protocol",
39		Hidden: true,
40		RunE: func(cmd *cobra.Command, args []string) error {
41			opts.Operation = args[0]
42
43			if runF != nil {
44				return runF(opts)
45			}
46			return helperRun(opts)
47		},
48	}
49
50	return cmd
51}
52
53func helperRun(opts *CredentialOptions) error {
54	if opts.Operation == "store" {
55		// We pretend to implement the "store" operation, but do nothing since we already have a cached token.
56		return cmdutil.SilentError
57	}
58
59	if opts.Operation != "get" {
60		return fmt.Errorf("gh auth git-credential: %q operation not supported", opts.Operation)
61	}
62
63	wants := map[string]string{}
64
65	s := bufio.NewScanner(opts.IO.In)
66	for s.Scan() {
67		line := s.Text()
68		if line == "" {
69			break
70		}
71		parts := strings.SplitN(line, "=", 2)
72		if len(parts) < 2 {
73			continue
74		}
75		key, value := parts[0], parts[1]
76		if key == "url" {
77			u, err := url.Parse(value)
78			if err != nil {
79				return err
80			}
81			wants["protocol"] = u.Scheme
82			wants["host"] = u.Host
83			wants["path"] = u.Path
84			wants["username"] = u.User.Username()
85			wants["password"], _ = u.User.Password()
86		} else {
87			wants[key] = value
88		}
89	}
90	if err := s.Err(); err != nil {
91		return err
92	}
93
94	if wants["protocol"] != "https" {
95		return cmdutil.SilentError
96	}
97
98	cfg, err := opts.Config()
99	if err != nil {
100		return err
101	}
102
103	var gotUser string
104	gotToken, source, _ := cfg.GetWithSource(wants["host"], "oauth_token")
105	if strings.HasSuffix(source, "_TOKEN") {
106		gotUser = tokenUser
107	} else {
108		gotUser, _, _ = cfg.GetWithSource(wants["host"], "user")
109	}
110
111	if gotUser == "" || gotToken == "" {
112		return cmdutil.SilentError
113	}
114
115	if wants["username"] != "" && gotUser != tokenUser && !strings.EqualFold(wants["username"], gotUser) {
116		return cmdutil.SilentError
117	}
118
119	fmt.Fprint(opts.IO.Out, "protocol=https\n")
120	fmt.Fprintf(opts.IO.Out, "host=%s\n", wants["host"])
121	fmt.Fprintf(opts.IO.Out, "username=%s\n", gotUser)
122	fmt.Fprintf(opts.IO.Out, "password=%s\n", gotToken)
123
124	return nil
125}
126