github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/auth/shared/oauth_scopes.go (about) 1 package shared 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "strings" 8 9 "github.com/ungtb10d/cli/v2/api" 10 "github.com/ungtb10d/cli/v2/internal/ghinstance" 11 ) 12 13 type MissingScopesError struct { 14 MissingScopes []string 15 } 16 17 func (e MissingScopesError) Error() string { 18 var missing []string 19 for _, s := range e.MissingScopes { 20 missing = append(missing, fmt.Sprintf("'%s'", s)) 21 } 22 scopes := strings.Join(missing, ", ") 23 24 if len(e.MissingScopes) == 1 { 25 return "missing required scope " + scopes 26 } 27 return "missing required scopes " + scopes 28 } 29 30 type httpClient interface { 31 Do(*http.Request) (*http.Response, error) 32 } 33 34 func GetScopes(httpClient httpClient, hostname, authToken string) (string, error) { 35 apiEndpoint := ghinstance.RESTPrefix(hostname) 36 37 req, err := http.NewRequest("GET", apiEndpoint, nil) 38 if err != nil { 39 return "", err 40 } 41 42 req.Header.Set("Authorization", "token "+authToken) 43 44 res, err := httpClient.Do(req) 45 if err != nil { 46 return "", err 47 } 48 49 defer func() { 50 // Ensure the response body is fully read and closed 51 // before we reconnect, so that we reuse the same TCPconnection. 52 _, _ = io.Copy(io.Discard, res.Body) 53 res.Body.Close() 54 }() 55 56 if res.StatusCode != 200 { 57 return "", api.HandleHTTPError(res) 58 } 59 60 return res.Header.Get("X-Oauth-Scopes"), nil 61 } 62 63 func HasMinimumScopes(httpClient httpClient, hostname, authToken string) error { 64 scopesHeader, err := GetScopes(httpClient, hostname, authToken) 65 if err != nil { 66 return err 67 } 68 69 if scopesHeader == "" { 70 // if the token reports no scopes, assume that it's an integration token and give up on 71 // detecting its capabilities 72 return nil 73 } 74 75 search := map[string]bool{ 76 "repo": false, 77 "read:org": false, 78 "admin:org": false, 79 } 80 for _, s := range strings.Split(scopesHeader, ",") { 81 search[strings.TrimSpace(s)] = true 82 } 83 84 var missingScopes []string 85 if !search["repo"] { 86 missingScopes = append(missingScopes, "repo") 87 } 88 89 if !search["read:org"] && !search["write:org"] && !search["admin:org"] { 90 missingScopes = append(missingScopes, "read:org") 91 } 92 93 if len(missingScopes) > 0 { 94 return &MissingScopesError{MissingScopes: missingScopes} 95 } 96 return nil 97 }