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