github.com/alexey-mercari/reviewdog@v0.10.1-0.20200514053941-928943b10766/doghouse/appengine/checker.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  
    12  	"github.com/reviewdog/reviewdog/doghouse"
    13  	"github.com/reviewdog/reviewdog/doghouse/server"
    14  	"github.com/reviewdog/reviewdog/doghouse/server/ciutil"
    15  	"github.com/reviewdog/reviewdog/doghouse/server/storage"
    16  )
    17  
    18  type githubChecker struct {
    19  	privateKey       []byte
    20  	integrationID    int
    21  	ghInstStore      storage.GitHubInstallationStore
    22  	ghRepoTokenStore storage.GitHubRepositoryTokenStore
    23  }
    24  
    25  func (gc *githubChecker) handleCheck(w http.ResponseWriter, r *http.Request) {
    26  	if r.Method != http.MethodPost {
    27  		w.WriteHeader(http.StatusMethodNotAllowed)
    28  		return
    29  	}
    30  	ctx := r.Context()
    31  
    32  	var req doghouse.CheckRequest
    33  	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
    34  		w.WriteHeader(http.StatusBadRequest)
    35  		fmt.Fprintf(w, "failed to decode request: %v", err)
    36  		return
    37  	}
    38  
    39  	// Check authorization.
    40  	if !gc.validateCheckRequest(ctx, w, r, req.Owner, req.Repo) {
    41  		return
    42  	}
    43  
    44  	opt := &server.NewGitHubClientOption{
    45  		PrivateKey:    gc.privateKey,
    46  		IntegrationID: gc.integrationID,
    47  		RepoOwner:     req.Owner,
    48  		Client:        &http.Client{},
    49  	}
    50  
    51  	gh, err := server.NewGitHubClient(ctx, opt)
    52  	if err != nil {
    53  		log.Printf("[ERROR] failed to create GitHub client: %v\n", err)
    54  		w.WriteHeader(http.StatusBadRequest)
    55  		fmt.Fprintln(w, err)
    56  		return
    57  	}
    58  
    59  	res, err := server.NewChecker(&req, gh).Check(ctx)
    60  	if err != nil {
    61  		log.Printf("[ERROR] failed to run checker: %v\n", err)
    62  		w.WriteHeader(http.StatusBadRequest)
    63  		fmt.Fprintln(w, err)
    64  		return
    65  	}
    66  	if err := json.NewEncoder(w).Encode(res); err != nil {
    67  		w.WriteHeader(http.StatusBadRequest)
    68  		fmt.Fprintln(w, err)
    69  		return
    70  	}
    71  }
    72  
    73  func (gc *githubChecker) validateCheckRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, owner, repo string) bool {
    74  	if extractBearerToken(r) == "" {
    75  		// Update Travis IP Address before checking IP to reduce the # of
    76  		// flaky errors when token is not present.
    77  		if err := ciutil.UpdateTravisCIIPAddrs(&http.Client{}); err != nil {
    78  			log.Printf("[ERROR] failed to update travis CI IP addresses: %v\n", err)
    79  		}
    80  	}
    81  	log.Printf("[INFO] Remote Addr: %s\n", r.RemoteAddr)
    82  	if ciutil.IsFromCI(r) {
    83  		// Skip token validation if it's from trusted CI providers.
    84  		return true
    85  	}
    86  	return gc.validateCheckToken(ctx, w, r, owner, repo)
    87  }
    88  
    89  func (gc *githubChecker) validateCheckToken(ctx context.Context, w http.ResponseWriter, r *http.Request, owner, repo string) bool {
    90  	token := extractBearerToken(r)
    91  	if token == "" {
    92  		w.Header().Set("The WWW-Authenticate", `error="invalid_request", error_description="The access token not provided"`)
    93  		w.WriteHeader(http.StatusUnauthorized)
    94  		fmt.Fprintf(w, "The access token not provided. Get token from %s", githubRepoURL(ctx, r, owner, repo))
    95  		return false
    96  	}
    97  	_, wantToken, err := gc.ghRepoTokenStore.Get(ctx, owner, repo)
    98  	if err != nil {
    99  		log.Printf("[ERROR] failed to get repository (%s/%s) token: %v\n", owner, repo, err)
   100  	}
   101  	if wantToken == nil {
   102  		w.WriteHeader(http.StatusNotFound)
   103  		return false
   104  	}
   105  	if token != wantToken.Token {
   106  		w.Header().Set("The WWW-Authenticate", `error="invalid_token", error_description="The access token is invalid"`)
   107  		w.WriteHeader(http.StatusUnauthorized)
   108  		fmt.Fprintf(w, "The access token is invalid. Get valid token from %s", githubRepoURL(ctx, r, owner, repo))
   109  		return false
   110  	}
   111  	return true
   112  }
   113  
   114  func githubRepoURL(ctx context.Context, r *http.Request, owner, repo string) string {
   115  	u := doghouseBaseURL(ctx, r)
   116  	u.Path = fmt.Sprintf("/gh/%s/%s", owner, repo)
   117  	return u.String()
   118  }
   119  
   120  func doghouseBaseURL(_ context.Context, r *http.Request) *url.URL {
   121  	scheme := ""
   122  	if r.URL != nil && r.URL.Scheme != "" {
   123  		scheme = r.URL.Scheme
   124  	}
   125  	if scheme == "" {
   126  		scheme = "https"
   127  	}
   128  	u, err := url.Parse(scheme + "://" + r.Host)
   129  	if err != nil {
   130  		log.Printf("[ERROR] %v\n", err)
   131  	}
   132  	return u
   133  }
   134  
   135  func extractBearerToken(r *http.Request) string {
   136  	auth := r.Header.Get("Authorization")
   137  	prefix := "bearer "
   138  	if strings.HasPrefix(strings.ToLower(auth), prefix) {
   139  		return auth[len(prefix):]
   140  	}
   141  	return ""
   142  }