github.com/secman-team/gh-api@v1.8.2/pkg/cmd/auth/shared/oauth_scopes.go (about)

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