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 }