Skip to content

Commit 5a110c8

Browse files
committed
Add SSH key generation & uploading to gh auth login flow
1 parent de5c04f commit 5a110c8

File tree

15 files changed

+684
-463
lines changed

15 files changed

+684
-463
lines changed

api/client.go

Lines changed: 11 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ type HTTPError struct {
142142
RequestURL *url.URL
143143
Message string
144144
OAuthScopes string
145+
Errors []HTTPErrorItem
146+
}
147+
148+
type HTTPErrorItem struct {
149+
Message string
150+
Resource string
151+
Field string
152+
Code string
145153
}
146154

147155
func (err HTTPError) Error() string {
@@ -153,79 +161,6 @@ func (err HTTPError) Error() string {
153161
return fmt.Sprintf("HTTP %d (%s)", err.StatusCode, err.RequestURL)
154162
}
155163

156-
type MissingScopesError struct {
157-
MissingScopes []string
158-
}
159-
160-
func (e MissingScopesError) Error() string {
161-
var missing []string
162-
for _, s := range e.MissingScopes {
163-
missing = append(missing, fmt.Sprintf("'%s'", s))
164-
}
165-
scopes := strings.Join(missing, ", ")
166-
167-
if len(e.MissingScopes) == 1 {
168-
return "missing required scope " + scopes
169-
}
170-
return "missing required scopes " + scopes
171-
}
172-
173-
func (c Client) HasMinimumScopes(hostname string) error {
174-
apiEndpoint := ghinstance.RESTPrefix(hostname)
175-
176-
req, err := http.NewRequest("GET", apiEndpoint, nil)
177-
if err != nil {
178-
return err
179-
}
180-
181-
req.Header.Set("Content-Type", "application/json; charset=utf-8")
182-
res, err := c.http.Do(req)
183-
if err != nil {
184-
return err
185-
}
186-
187-
defer func() {
188-
// Ensure the response body is fully read and closed
189-
// before we reconnect, so that we reuse the same TCPconnection.
190-
_, _ = io.Copy(ioutil.Discard, res.Body)
191-
res.Body.Close()
192-
}()
193-
194-
if res.StatusCode != 200 {
195-
return HandleHTTPError(res)
196-
}
197-
198-
scopesHeader := res.Header.Get("X-Oauth-Scopes")
199-
if scopesHeader == "" {
200-
// if the token reports no scopes, assume that it's an integration token and give up on
201-
// detecting its capabilities
202-
return nil
203-
}
204-
205-
search := map[string]bool{
206-
"repo": false,
207-
"read:org": false,
208-
"admin:org": false,
209-
}
210-
for _, s := range strings.Split(scopesHeader, ",") {
211-
search[strings.TrimSpace(s)] = true
212-
}
213-
214-
var missingScopes []string
215-
if !search["repo"] {
216-
missingScopes = append(missingScopes, "repo")
217-
}
218-
219-
if !search["read:org"] && !search["admin:org"] {
220-
missingScopes = append(missingScopes, "read:org")
221-
}
222-
223-
if len(missingScopes) > 0 {
224-
return &MissingScopesError{MissingScopes: missingScopes}
225-
}
226-
return nil
227-
}
228-
229164
// GraphQL performs a GraphQL request and parses the response
230165
func (c Client) GraphQL(hostname string, query string, variables map[string]interface{}, data interface{}) error {
231166
reqBody, err := json.Marshal(map[string]interface{}{"query": query, "variables": variables})
@@ -341,22 +276,16 @@ func HandleHTTPError(resp *http.Response) error {
341276
return httpError
342277
}
343278

344-
type errorObject struct {
345-
Message string
346-
Resource string
347-
Field string
348-
Code string
349-
}
350-
351279
messages := []string{parsedBody.Message}
352280
for _, raw := range parsedBody.Errors {
353281
switch raw[0] {
354282
case '"':
355283
var errString string
356284
_ = json.Unmarshal(raw, &errString)
357285
messages = append(messages, errString)
286+
httpError.Errors = append(httpError.Errors, HTTPErrorItem{Message: errString})
358287
case '{':
359-
var errInfo errorObject
288+
var errInfo HTTPErrorItem
360289
_ = json.Unmarshal(raw, &errInfo)
361290
msg := errInfo.Message
362291
if errInfo.Code != "custom" {
@@ -365,6 +294,7 @@ func HandleHTTPError(resp *http.Response) error {
365294
if msg != "" {
366295
messages = append(messages, msg)
367296
}
297+
httpError.Errors = append(httpError.Errors, errInfo)
368298
}
369299
}
370300
httpError.Message = strings.Join(messages, "\n")

api/client_test.go

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -109,67 +109,3 @@ func TestRESTError(t *testing.T) {
109109

110110
}
111111
}
112-
113-
func Test_HasMinimumScopes(t *testing.T) {
114-
tests := []struct {
115-
name string
116-
header string
117-
wantErr string
118-
}{
119-
{
120-
name: "no scopes",
121-
header: "",
122-
wantErr: "",
123-
},
124-
{
125-
name: "default scopes",
126-
header: "repo, read:org",
127-
wantErr: "",
128-
},
129-
{
130-
name: "admin:org satisfies read:org",
131-
header: "repo, admin:org",
132-
wantErr: "",
133-
},
134-
{
135-
name: "insufficient scope",
136-
header: "repo",
137-
wantErr: "missing required scope 'read:org'",
138-
},
139-
{
140-
name: "insufficient scopes",
141-
header: "gist",
142-
wantErr: "missing required scopes 'repo', 'read:org'",
143-
},
144-
}
145-
for _, tt := range tests {
146-
t.Run(tt.name, func(t *testing.T) {
147-
fakehttp := &httpmock.Registry{}
148-
client := NewClient(ReplaceTripper(fakehttp))
149-
150-
fakehttp.Register(httpmock.REST("GET", ""), func(req *http.Request) (*http.Response, error) {
151-
return &http.Response{
152-
Request: req,
153-
StatusCode: 200,
154-
Body: ioutil.NopCloser(&bytes.Buffer{}),
155-
Header: map[string][]string{
156-
"X-Oauth-Scopes": {tt.header},
157-
},
158-
}, nil
159-
})
160-
161-
err := client.HasMinimumScopes("github.com")
162-
if tt.wantErr == "" {
163-
if err != nil {
164-
t.Errorf("error: %v", err)
165-
}
166-
return
167-
}
168-
if err.Error() != tt.wantErr {
169-
t.Errorf("want %q, got %q", tt.wantErr, err.Error())
170-
171-
}
172-
})
173-
}
174-
175-
}

internal/authflow/flow.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"strings"
1010

1111
"github.com/cli/cli/api"
12-
"github.com/cli/cli/internal/config"
1312
"github.com/cli/cli/internal/ghinstance"
1413
"github.com/cli/cli/pkg/browser"
1514
"github.com/cli/cli/pkg/iostreams"
@@ -23,7 +22,12 @@ var (
2322
oauthClientSecret = "34ddeff2b558a23d38fba8a6de74f086ede1cc0b"
2423
)
2524

26-
func AuthFlowWithConfig(cfg config.Config, IO *iostreams.IOStreams, hostname, notice string, additionalScopes []string) (string, error) {
25+
type iconfig interface {
26+
Set(string, string, string) error
27+
Write() error
28+
}
29+
30+
func AuthFlowWithConfig(cfg iconfig, IO *iostreams.IOStreams, hostname, notice string, additionalScopes []string) (string, error) {
2731
// TODO this probably shouldn't live in this package. It should probably be in a new package that
2832
// depends on both iostreams and config.
2933
stderr := IO.ErrOut

0 commit comments

Comments
 (0)