prow/prstatus/prstatus.go
func (da *DashboardAgent) HandlePrStatus(queryHandler PullRequestQueryHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		serverError := func(action string, err error) {
			da.log.WithError(err).Errorf("Error %s.", action)
			msg := fmt.Sprintf("500 Internal server error %s: %v", action, err)
			http.Error(w, msg, http.StatusInternalServerError)
		}

		data := UserData{
			Login: false,
		}

		// Get existing session. Invalidate everything if we fail and continue as
		// if not logged in.
		session, err := da.goac.CookieStore.Get(r, tokenSession)
		if err != nil {
			da.log.WithError(err).Info("Failed to get existing session, invalidating GitHub login session")
			if err := invalidateGitHubSession(w, r, session); err != nil {
				serverError("Failed to invalidate GitHub session", err)
				return
			}
		}

		// If access token exists, get user login using the access token. This is a
		// chance to validate whether the access token is consumable or not. If
		// not, we invalidate the sessions and continue as if not logged in.
		token, ok := session.Values[tokenKey].(*oauth2.Token)
		var user *github.User
		var botName string
		if ok && token.Valid() {
			githubClient := github.NewClient(func() []byte { return []byte(token.AccessToken) }, github.DefaultGraphQLEndpoint, github.DefaultAPIEndpoint)
			var err error
			botName, err = githubClient.BotName()
			user = &github.User{Login: botName}
			if err != nil {
				if strings.Contains(err.Error(), "401") {
					da.log.Info("Failed to access GitHub with existing access token, invalidating GitHub login session")
					if err := invalidateGitHubSession(w, r, session); err != nil {
						serverError("Failed to invalidate GitHub session", err)
						return
					}
				} else {
					serverError("Error with getting user login", err)
					return
				}
			}
		}

		if user != nil {
			login := user.Login
			data.Login = true
			// Saves login. We save the login under 2 cookies. One for the use of client to render the
			// data and one encoded for server to verify the identity of the authenticated user.
			http.SetCookie(w, &http.Cookie{
				Name:    loginSession,
				Value:   login,
				Path:    "/",
				Expires: time.Now().Add(time.Hour * 24 * 30),
				Secure:  true,
			})
			session.Values[loginKey] = login
			if err := session.Save(r, w); err != nil {
				serverError("Save oauth session", err)
				return
			}

			// Construct query
			ghc := github.NewClient(func() []byte { return []byte(token.AccessToken) }, github.DefaultGraphQLEndpoint, github.DefaultAPIEndpoint)
			query := da.ConstructSearchQuery(login)
			if err := r.ParseForm(); err == nil {
				if q := r.Form.Get("query"); q != "" {
					query = q
				}
			}
			// If neither repo nor org is specified in the search query. We limit the search to repos that
			// are configured with either Prow or Tide.
			if !queryConstrainsRepos(query) {
				for _, v := range da.repos {
					query += fmt.Sprintf(" repo:\"%s\"", v)
				}
			}
			pullRequests, err := queryHandler.QueryPullRequests(context.Background(), ghc, query)
			if err != nil {
				serverError("Error with querying user data.", err)
				return
			}
			var pullRequestWithContexts []PullRequestWithContexts
			for _, pr := range pullRequests {
				prcontexts, err := queryHandler.GetHeadContexts(ghc, pr)
				if err != nil {
					serverError("Error with getting head context of pr", err)
					continue
				}
				pullRequestWithContexts = append(pullRequestWithContexts, PullRequestWithContexts{
					Contexts:    prcontexts,
					PullRequest: pr,
				})
			}

			data.PullRequestsWithContexts = pullRequestWithContexts
		}

		marshaledData, err := json.Marshal(data)
		if err != nil {
			da.log.WithError(err).Error("Error with marshalling user data.")
		}

		if v := r.URL.Query().Get("var"); v != "" {
			fmt.Fprintf(w, "var %s = ", v)
			w.Write(marshaledData)
			io.WriteString(w, ";")
		} else {
			w.Write(marshaledData)
		}
	}
}
func (da *DashboardAgent) HandlePrStatus(queryHandler PullRequestQueryHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		serverError := func(action string, err error) {
			da.log.WithError(err).Errorf("Error %s.", action)
			msg := fmt.Sprintf("500 Internal server error %s: %v", action, err)
			http.Error(w, msg, http.StatusInternalServerError)
		}

		data := UserData{
			Login: false,
		}

		// Get existing session. Invalidate everything if we fail and continue as
		// if not logged in.
		session, err := da.goac.CookieStore.Get(r, tokenSession)
		if err != nil {
			da.log.WithError(err).Info("Failed to get existing session, invalidating GitHub login session")
			if err := invalidateGitHubSession(w, r, session); err != nil {
				serverError("Failed to invalidate GitHub session", err)
				return
			}
		}

		// If access token exists, get user login using the access token. This is a
		// chance to validate whether the access token is consumable or not. If
		// not, we invalidate the sessions and continue as if not logged in.
		token, ok := session.Values[tokenKey].(*oauth2.Token)
		var user *github.User
		var botName string
		if ok && token.Valid() {
			githubClient := github.NewClient(func() []byte { return []byte(token.AccessToken) }, github.DefaultGraphQLEndpoint, github.DefaultAPIEndpoint)
			var err error
			botName, err = githubClient.BotName()
			user = &github.User{Login: botName}
			if err != nil {
				if strings.Contains(err.Error(), "401") {
					da.log.Info("Failed to access GitHub with existing access token, invalidating GitHub login session")
					if err := invalidateGitHubSession(w, r, session); err != nil {
						serverError("Failed to invalidate GitHub session", err)
						return
					}
				} else {
					serverError("Error with getting user login", err)
					return
				}
			}
		}

		if user != nil {
			login := user.Login
			data.Login = true
			// Saves login. We save the login under 2 cookies. One for the use of client to render the
			// data and one encoded for server to verify the identity of the authenticated user.
			http.SetCookie(w, &http.Cookie{
				Name:    loginSession,
				Value:   login,
				Path:    "/",
				Expires: time.Now().Add(time.Hour * 24 * 30),
				Secure:  true,
			})
			session.Values[loginKey] = login
			if err := session.Save(r, w); err != nil {
				serverError("Save oauth session", err)
				return
			}

			// Construct query
			ghc := github.NewClient(func() []byte { return []byte(token.AccessToken) }, github.DefaultGraphQLEndpoint, github.DefaultAPIEndpoint)
			query := da.ConstructSearchQuery(login)
			if err := r.ParseForm(); err == nil {
				if q := r.Form.Get("query"); q != "" {
					query = q
				}
			}
			// If neither repo nor org is specified in the search query. We limit the search to repos that
			// are configured with either Prow or Tide.
			if !queryConstrainsRepos(query) {
				for _, v := range da.repos {
					query += fmt.Sprintf(" repo:\"%s\"", v)
				}
			}
			pullRequests, err := queryHandler.QueryPullRequests(context.Background(), ghc, query)
			if err != nil {
				serverError("Error with querying user data.", err)
				return
			}
			var pullRequestWithContexts []PullRequestWithContexts
			for _, pr := range pullRequests {
				prcontexts, err := queryHandler.GetHeadContexts(ghc, pr)
				if err != nil {
					serverError("Error with getting head context of pr", err)
					continue
				}
				pullRequestWithContexts = append(pullRequestWithContexts, PullRequestWithContexts{
					Contexts:    prcontexts,
					PullRequest: pr,
				})
			}

			data.PullRequestsWithContexts = pullRequestWithContexts
		}

		marshaledData, err := json.Marshal(data)
		if err != nil {
			da.log.WithError(err).Error("Error with marshalling user data.")
		}

		if v := r.URL.Query().Get("var"); v != "" {
			fmt.Fprintf(w, "var %s = ", v)
			w.Write(marshaledData)
			io.WriteString(w, ";")
		} else {
			w.Write(marshaledData)
		}
	}
}
// HandlePrStatus returns a http handler function that handles request to /pr-status
// endpoint. The handler takes user access token stored in the cookie to query to GitHub on behalf
// of the user and serve the data in return. The Query handler is passed to the method so as it
// can be mocked in the unit test..