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