diff --git a/prow/cmd/deck/main.go b/prow/cmd/deck/main.go index dec4d42c7e4a5..4ef665a6acf01 100644 --- a/prow/cmd/deck/main.go +++ b/prow/cmd/deck/main.go @@ -394,7 +394,7 @@ func prodOnlyMain(cfg config.Getter, o options, mux *http.ServeMux) *http.ServeM mux.Handle("/prowjobs.js", gziphandler.GzipHandler(handleProwJobs(ja))) mux.Handle("/badge.svg", gziphandler.GzipHandler(handleBadge(ja))) mux.Handle("/log", gziphandler.GzipHandler(handleLog(ja))) - mux.Handle("/rerun", gziphandler.GzipHandler(handleRerun(prowJobClient, o.rerunCreatesJob))) + mux.Handle("/rerun", gziphandler.GzipHandler(handleRerun(prowJobClient, o.rerunCreatesJob, cfg))) mux.Handle("/prowjob", gziphandler.GzipHandler(handleProwJob(prowJobClient))) if o.spyglass { @@ -1150,7 +1150,22 @@ func handleProwJob(prowJobClient prowv1.ProwJobInterface) http.HandlerFunc { } } -func handleRerun(prowJobClient prowv1.ProwJobInterface, createProwJob bool) http.HandlerFunc { +// canTriggerJob determines whether the given user can trigger any job. +func canTriggerJob(user string, cfg config.Getter) bool { + if cfg().Deck.RerunAuthConfig.AllowAnyone { + return true + } + if cfg().Deck.RerunAuthConfig.AuthorizedUsers != nil { + for _, s := range cfg().Deck.RerunAuthConfig.AuthorizedUsers { + if user == s { + return true + } + } + } + return false +} + +func handleRerun(prowJobClient prowv1.ProwJobInterface, createProwJob bool, cfg config.Getter) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("prowjob") if name == "" { @@ -1177,13 +1192,25 @@ func handleRerun(prowJobClient prowv1.ProwJobInterface, createProwJob bool) http http.Error(w, "request must be of type POST", http.StatusMethodNotAllowed) return } - if _, err := prowJobClient.Create(&newPJ); err != nil { - logrus.WithError(err).Error("Error creating job") - http.Error(w, fmt.Sprintf("Error creating job: %v", err), http.StatusInternalServerError) + githubCookie, err := r.Cookie("github_login") + if err == http.ErrNoCookie { + http.Redirect(w, r, "/github-login", http.StatusFound) + } else if err != nil { + http.Error(w, fmt.Sprintf("Error retrieving GitHub cookie: %v", err), http.StatusInternalServerError) + return + } + if canTriggerJob(githubCookie.Value, cfg) { + if _, err := prowJobClient.Create(&newPJ); err != nil { + logrus.WithError(err).Error("Error creating job") + http.Error(w, fmt.Sprintf("Error creating job: %v", err), http.StatusInternalServerError) + return + } + http.Redirect(w, r, "/?rerun=success", http.StatusFound) + return + } else { + http.Redirect(w, r, "/?rerun=denied", http.StatusFound) return } - w.WriteHeader(http.StatusNoContent) - return } b, err := yaml.Marshal(&newPJ) if err != nil { diff --git a/prow/cmd/deck/main_test.go b/prow/cmd/deck/main_test.go index 2e5e5d5096e73..8e88e85032431 100644 --- a/prow/cmd/deck/main_test.go +++ b/prow/cmd/deck/main_test.go @@ -258,15 +258,43 @@ func TestProwJob(t *testing.T) { func TestRerun(t *testing.T) { testCases := []struct { name string + login string + allowAnyone bool + rerunCreatesJob bool shouldCreateProwjob bool + httpCode int }{ { name: "Handler returns ProwJob", + login: "hello", + allowAnyone: false, + rerunCreatesJob: true, + shouldCreateProwjob: true, + httpCode: http.StatusFound, + }, + { + name: "User not authorized to create prow job", + login: "malicious", + allowAnyone: false, + rerunCreatesJob: true, + shouldCreateProwjob: false, + httpCode: http.StatusFound, + }, + { + name: "RerunCreatesJob set to false, should not create prow job", + login: "hello", + allowAnyone: true, + rerunCreatesJob: false, shouldCreateProwjob: false, + httpCode: http.StatusOK, }, { - name: "Handler creates ProwJob", + name: "Allow anyone set to true, creates job", + login: "ugh", + allowAnyone: true, + rerunCreatesJob: true, shouldCreateProwjob: true, + httpCode: http.StatusFound, }, } @@ -292,18 +320,37 @@ func TestRerun(t *testing.T) { State: prowapi.PendingState, }, }) - handler := handleRerun(fakeProwJobClient.ProwV1().ProwJobs("prowjobs"), tc.shouldCreateProwjob) + configGetter := func() *config.Config { + return &config.Config{ + ProwConfig: config.ProwConfig{ + Deck: config.Deck{ + RerunAuthConfig: config.RerunAuthConfig{ + AllowAnyone: tc.allowAnyone, + AuthorizedUsers: []string{"hello", "world"}, + }, + }, + }, + } + } + handler := handleRerun(fakeProwJobClient.ProwV1().ProwJobs("prowjobs"), tc.rerunCreatesJob, configGetter) req, err := http.NewRequest(http.MethodPost, "/rerun?prowjob=wowsuch", nil) + req.AddCookie(&http.Cookie{ + Name: "github_login", + Value: tc.login, + Path: "/", + Expires: time.Now().Add(time.Hour * 24 * 30), + Secure: true, + }) if err != nil { t.Fatalf("Error making request: %v", err) } rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) + if rr.Code != tc.httpCode { + t.Fatalf("Bad error code: %d", rr.Code) + } if tc.shouldCreateProwjob { - if rr.Code != http.StatusNoContent { - t.Fatalf("Unexpected http status code: %d, expected 204", rr.Code) - } pjs, err := fakeProwJobClient.ProwV1().ProwJobs("prowjobs").List(metav1.ListOptions{}) if err != nil { t.Fatalf("failed to list prowjobs: %v", err) @@ -312,10 +359,7 @@ func TestRerun(t *testing.T) { t.Errorf("expected to get two prowjobs, got %d", numPJs) } - } else { - if rr.Code != http.StatusOK { - t.Fatalf("Bad error code: %d", rr.Code) - } + } else if !tc.rerunCreatesJob { resp := rr.Result() defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) diff --git a/prow/cmd/deck/static/prow/prow.ts b/prow/cmd/deck/static/prow/prow.ts index 198ba0d1fe63d..f95373790fe17 100644 --- a/prow/cmd/deck/static/prow/prow.ts +++ b/prow/cmd/deck/static/prow/prow.ts @@ -650,7 +650,13 @@ function redraw(fz: FuzzySearch): void { max = 2 * 3600; } drawJobHistogram(totalJob, jobHistogram, now - (12 * 3600), now, max); - if (rerunStatus != null) { + if (rerunStatus === "denied") { + modal.style.display = "block"; + rerunCommand.innerHTML = `You don't have permission to rerun that job. Try asking @cjwagner.`; + } else if (rerunStatus === "success") { + modal.style.display = "block"; + rerunCommand.innerHTML = `Job successfully triggered`; + } else if (rerunStatus != null) { modal.style.display = "block"; rerunCommand.innerHTML = `Nice try! The direct rerun feature hasn't been implemented yet, so that button does nothing.`; } @@ -671,7 +677,7 @@ function createRerunCell(modal: HTMLElement, rerunElement: HTMLElement, prowjob: copyButton.innerHTML = "file_copy"; rerunElement.appendChild(copyButton); const runButton = document.createElement('a'); - runButton.innerHTML = ""; + runButton.innerHTML = ""; if (login === "") { runButton.href = `/github-login?dest=%2F?rerun=work_in_progress`; } else {