github.com/mistwind/reviewdog@v0.0.0-20230322024206-9cfa11856d58/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/mistwind/reviewdog/doghouse" 14 "github.com/mistwind/reviewdog/doghouse/server" 15 "github.com/mistwind/reviewdog/doghouse/server/ciutil" 16 "github.com/mistwind/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 http.Error(w, fmt.Sprintf("failed to decode request: %v", err), http.StatusBadRequest) 37 return 38 } 39 40 // Check authorization. 41 if !gc.validateCheckRequest(ctx, w, r, req.Owner, req.Repo) { 42 return 43 } 44 45 opt := &server.NewGitHubClientOption{ 46 PrivateKey: gc.privateKey, 47 IntegrationID: gc.integrationID, 48 RepoOwner: req.Owner, 49 Client: &http.Client{ 50 Transport: gc.tr, 51 }, 52 } 53 54 gh, err := server.NewGitHubClient(ctx, opt) 55 if err != nil { 56 aelog.Errorf(ctx, "failed to create GitHub client: %v", err) 57 w.WriteHeader(http.StatusBadRequest) 58 fmt.Fprintln(w, err) 59 return 60 } 61 62 res, err := server.NewChecker(&req, gh).Check(ctx) 63 if err != nil { 64 aelog.Errorf(ctx, "failed to run checker: %v", err) 65 w.WriteHeader(http.StatusBadRequest) 66 fmt.Fprintln(w, err) 67 return 68 } 69 if err := json.NewEncoder(w).Encode(res); err != nil { 70 w.WriteHeader(http.StatusBadRequest) 71 fmt.Fprintln(w, err) 72 return 73 } 74 } 75 76 func (gc *githubChecker) validateCheckRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, owner, repo string) bool { 77 if extractBearerToken(r) == "" { 78 // Update Travis IP Address before checking IP to reduce the # of 79 // flaky errors when token is not present. 80 if err := ciutil.UpdateTravisCIIPAddrs(&http.Client{}); err != nil { 81 aelog.Errorf(ctx, "failed to update travis CI IP addresses: %v", err) 82 } 83 } 84 aelog.Infof(ctx, "Remote Addr: %s", ciutil.IPFromReq(r)) 85 if ciutil.IsFromCI(r) { 86 // Skip token validation if it's from trusted CI providers. 87 return true 88 } 89 return gc.validateCheckToken(ctx, w, r, owner, repo) 90 } 91 92 func (gc *githubChecker) validateCheckToken(ctx context.Context, w http.ResponseWriter, r *http.Request, owner, repo string) bool { 93 token := extractBearerToken(r) 94 if token == "" { 95 w.Header().Set("The WWW-Authenticate", `error="invalid_request", error_description="The access token not provided"`) 96 msg := fmt.Sprintf("The access token not provided. Get token from %s", githubRepoURL(ctx, r, owner, repo)) 97 http.Error(w, msg, http.StatusUnauthorized) 98 return false 99 } 100 _, wantToken, err := gc.ghRepoTokenStore.Get(ctx, owner, repo) 101 if err != nil { 102 aelog.Errorf(ctx, "failed to get repository (%s/%s) token: %v", owner, repo, err) 103 } 104 if wantToken == nil { 105 w.WriteHeader(http.StatusNotFound) 106 return false 107 } 108 if token != wantToken.Token { 109 w.Header().Set("The WWW-Authenticate", `error="invalid_token", error_description="The access token is invalid"`) 110 msg := fmt.Sprintf("The access token is invalid. Get valid token from %s", githubRepoURL(ctx, r, owner, repo)) 111 http.Error(w, msg, http.StatusUnauthorized) 112 return false 113 } 114 return true 115 } 116 117 func githubRepoURL(ctx context.Context, r *http.Request, owner, repo string) string { 118 u := doghouseBaseURL(ctx, r) 119 u.Path = fmt.Sprintf("/gh/%s/%s", owner, repo) 120 return u.String() 121 } 122 123 func doghouseBaseURL(ctx context.Context, r *http.Request) *url.URL { 124 scheme := "" 125 if r.URL != nil && r.URL.Scheme != "" { 126 scheme = r.URL.Scheme 127 } 128 if scheme == "" { 129 scheme = "https" 130 } 131 u, err := url.Parse(scheme + "://" + r.Host) 132 if err != nil { 133 aelog.Errorf(ctx, "%v", err) 134 } 135 return u 136 } 137 138 func extractBearerToken(r *http.Request) string { 139 auth := r.Header.Get("Authorization") 140 prefix := "bearer " 141 if strings.HasPrefix(strings.ToLower(auth), prefix) { 142 return auth[len(prefix):] 143 } 144 return "" 145 }