Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(72)

Side by Side Diff: tools/bug_chomper/src/server/server.go

Issue 274693002: BugChomper utility - rewrite in Go (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Address more comments, add error.html Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /*
6 Serves a webpage for easy management of Skia bugs.
7
8 WARNING: This server is NOT secure and should not be made publicly
9 accessible.
10 */
11
12 package main
13
14 import (
15 "encoding/json"
16 "flag"
17 "fmt"
18 "html/template"
19 "issue_tracker"
20 "log"
21 "net/http"
22 "net/url"
23 "path"
24 "path/filepath"
25 "strconv"
26 "strings"
27 "time"
28 )
29
30 import "github.com/gorilla/securecookie"
31
32 const certFile = "certs/cert.pem"
33 const keyFile = "certs/key.pem"
34 const issueComment = "Edited by BugChomper"
35 const oauthCallbackPath = "/oauth2callback"
36 const oauthConfigFile = "oauth_client_secret.json"
37 const defaultPort = 8000
38 const localHost = "127.0.0.1"
39 const maxSessionLen = time.Duration(3600 * time.Second)
40 const priorityPrefix = "Priority-"
41 const project = "skia"
42 const cookieName = "BugChomperCookie"
43
44 var scheme = "http"
45
46 var curdir, _ = filepath.Abs(".")
47 var templatePath, _ = filepath.Abs("templates")
48 var templates = template.Must(template.ParseFiles(
49 path.Join(templatePath, "bug_chomper.html"),
50 path.Join(templatePath, "submitted.html"),
51 path.Join(templatePath, "error.html")))
52
53 var hashKey = securecookie.GenerateRandomKey(32)
54 var blockKey = securecookie.GenerateRandomKey(32)
55 var secureCookie = securecookie.New(hashKey, blockKey)
56
57 // SessionState contains data for a given session.
58 type SessionState struct {
59 IssueTracker *issue_tracker.IssueTracker
60 OrigRequestURL string
61 SessionStart time.Time
62 }
63
64 // getAbsoluteURL returns the absolute URL of the given Request.
65 func getAbsoluteURL(r *http.Request) string {
66 return scheme + "://" + r.Host + r.URL.Path
67 }
68
69 // getOAuth2CallbackURL returns a callback URL to be used by the OAuth2 login
70 // page.
71 func getOAuth2CallbackURL(r *http.Request) string {
72 return scheme + "://" + r.Host + oauthCallbackPath
73 }
74
75 func saveSession(session *SessionState, w http.ResponseWriter, r *http.Request) error {
76 encodedSession, err := secureCookie.Encode(cookieName, session)
77 if err != nil {
78 return fmt.Errorf("unable to encode session state: %s", err)
79 }
80 cookie := &http.Cookie{
81 Name: cookieName,
82 Value: encodedSession,
83 Domain: strings.Split(r.Host, ":")[0],
84 Path: "/",
85 HttpOnly: true,
86 }
87 http.SetCookie(w, cookie)
88 return nil
89 }
90
91 // makeSession creates a new session for the Request.
92 func makeSession(
jcgregorio 2014/05/13 17:50:17 nit, keep this one line.
borenet 2014/05/13 17:57:08 Done here and elsewhere.
93 w http.ResponseWriter, r *http.Request) (*SessionState, error) {
94 log.Println("Creating new session.")
95 // Create the session state.
96 issueTracker, err := issue_tracker.MakeIssueTracker(
97 oauthConfigFile, getOAuth2CallbackURL(r))
98 if err != nil {
99 return nil, fmt.Errorf("unable to create IssueTracker for sessio n: %s", err)
100 }
101 session := SessionState{
102 IssueTracker: issueTracker,
103 OrigRequestURL: getAbsoluteURL(r),
104 SessionStart: time.Now(),
105 }
106
107 // Encode and store the session state.
108 if err := saveSession(&session, w, r); err != nil {
109 return nil, err
110 }
111
112 return &session, nil
113 }
114
115 // getSession retrieves the active SessionState or creates and returns a new
116 // SessionState.
117 func getSession(
jcgregorio 2014/05/13 17:50:17 nit, keep this one line.
118 w http.ResponseWriter, r *http.Request) (*SessionState, error) {
119 cookie, err := r.Cookie(cookieName)
120 if err != nil {
121 log.Println("No cookie found! Starting new session.")
122 return makeSession(w, r)
123 }
124 var session SessionState
125 if err := secureCookie.Decode(cookieName, cookie.Value, &session); err ! = nil {
126 log.Printf("Invalid or corrupted session. Starting another: %s", err.Error())
127 return makeSession(w, r)
128 }
129
130 currentTime := time.Now()
131 if currentTime.Sub(session.SessionStart) > maxSessionLen {
132 log.Printf("Session starting at %s is expired. Starting another. ",
133 session.SessionStart.Format(time.RFC822))
134 return makeSession(w, r)
135 }
136 saveSession(&session, w, r)
137 return &session, nil
138 }
139
140 // reportError serves the error page with the given message.
141 func reportError(w http.ResponseWriter, msg string, code int) {
142 errData := struct {
143 Code int
144 CodeString string
145 Message string
146 }{
147 Code: code,
148 CodeString: http.StatusText(code),
149 Message: msg,
150 }
151 w.WriteHeader(code)
152 err := templates.ExecuteTemplate(w, "error.html", errData)
153 if err != nil {
154 log.Println("Failed to display error.html!!")
155 }
156 }
157
158 // makeBugChomperPage builds and serves the BugChomper page.
159 func makeBugChomperPage(w http.ResponseWriter, r *http.Request) {
160 session, err := getSession(w, r)
161 if err != nil {
162 reportError(w, err.Error(), http.StatusInternalServerError)
163 return
164 }
165 issueTracker := session.IssueTracker
166 user, err := issueTracker.GetLoggedInUser()
167 if err != nil {
168 reportError(w, err.Error(), http.StatusInternalServerError)
169 return
170 }
171 log.Println("Loading bugs for " + user)
172 bugList, err := issueTracker.GetBugs(project, user)
173 if err != nil {
174 reportError(w, err.Error(), http.StatusInternalServerError)
175 return
176 }
177 bugsById := make(map[string]*issue_tracker.Issue)
178 bugsByPriority := make(map[string][]*issue_tracker.Issue)
179 for _, bug := range bugList.Items {
180 bugsById[strconv.Itoa(bug.Id)] = bug
181 var bugPriority string
182 for _, label := range bug.Labels {
183 if strings.HasPrefix(label, priorityPrefix) {
184 bugPriority = label[len(priorityPrefix):]
185 }
186 }
187 if _, ok := bugsByPriority[bugPriority]; !ok {
188 bugsByPriority[bugPriority] = make(
189 []*issue_tracker.Issue, 0)
190 }
191 bugsByPriority[bugPriority] = append(
192 bugsByPriority[bugPriority], bug)
193 }
194 bugsJson, err := json.Marshal(bugsById)
195 if err != nil {
196 reportError(w, err.Error(), http.StatusInternalServerError)
197 return
198 }
199 data := struct {
200 Title string
201 User string
202 BugsJson template.JS
203 BugsByPriority *map[string][]*issue_tracker.Issue
204 Priorities []string
205 PriorityPrefix string
206 }{
207 Title: "BugChomper",
208 User: user,
209 BugsJson: template.JS(string(bugsJson)),
210 BugsByPriority: &bugsByPriority,
211 Priorities: issue_tracker.BugPriorities,
212 PriorityPrefix: priorityPrefix,
213 }
214
215 if err:= templates.ExecuteTemplate(w, "bug_chomper.html", data); err != nil {
216 reportError(w, err.Error(), http.StatusInternalServerError)
217 return
218 }
219 }
220
221 // authIfNeeded determines whether the current user is logged in. If not, it
222 // redirects to a login page. Returns true if the user is redirected and false
223 // otherwise.
224 func authIfNeeded(w http.ResponseWriter, r *http.Request) bool {
225 session, err := getSession(w, r)
226 if err != nil {
227 reportError(w, err.Error(), http.StatusInternalServerError)
228 return false
229 }
230 issueTracker := session.IssueTracker
231 if !issueTracker.IsAuthenticated() {
232 loginURL := issueTracker.MakeAuthRequestURL()
233 log.Println("Redirecting for login:", loginURL)
234 http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect)
235 return true
236 }
237 return false
238 }
239
240 // submitData attempts to submit data from a POST request to the IssueTracker.
241 func submitData(w http.ResponseWriter, r *http.Request) {
242 session, err := getSession(w, r)
243 if err != nil {
244 reportError(w, err.Error(), http.StatusInternalServerError)
245 return
246 }
247 issueTracker := session.IssueTracker
248 edits := r.FormValue("all_edits")
249 var editsMap map[string]*issue_tracker.Issue
250 if err := json.Unmarshal([]byte(edits), &editsMap); err != nil {
251 errMsg := "Could not parse edits from form response: " + err.Err or()
252 reportError(w, errMsg, http.StatusInternalServerError)
253 return
254 }
255 data := struct {
256 Title string
257 Message string
258 BackLink string
259 }{}
260 if len(editsMap) == 0 {
261 data.Title = "No Changes Submitted"
262 data.Message = "You didn't change anything!"
263 data.BackLink = ""
264 if err := templates.ExecuteTemplate(w, "submitted.html", data); err != nil {
265 reportError(w, err.Error(), http.StatusInternalServerErr or)
266 return
267 }
268 return
269 }
270 errorList := make([]error, 0)
271 for issueId, newIssue := range editsMap {
272 log.Println("Editing issue " + issueId)
273 if err := issueTracker.SubmitIssueChanges(newIssue, issueComment ); err != nil {
274 errorList = append(errorList, err)
275 }
276 }
277 if len(errorList) > 0 {
278 errorStrings := ""
279 for _, err := range errorList {
280 errorStrings += err.Error() + "\n"
281 }
282 errMsg := "Not all changes could be submitted: \n" + errorString s
283 reportError(w, errMsg, http.StatusInternalServerError)
284 return
285 }
286 data.Title = "Submitted Changes"
287 data.Message = "Your changes were submitted to the issue tracker."
288 data.BackLink = ""
289 if err := templates.ExecuteTemplate(w, "submitted.html", data); err != n il {
290 reportError(w, err.Error(), http.StatusInternalServerError)
291 return
292 }
293 return
294 }
295
296 // handleBugChomper handles HTTP requests for the bug_chomper page.
297 func handleBugChomper(w http.ResponseWriter, r *http.Request) {
298 if authIfNeeded(w, r) {
299 return
300 }
301 switch r.Method {
302 case "GET":
303 makeBugChomperPage(w, r)
304 case "POST":
305 submitData(w, r)
306 }
307 }
308
309 // handleOAuth2Callback handles callbacks from the OAuth2 sign-in.
310 func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
311 session, err := getSession(w, r)
312 if err != nil {
313 reportError(w, err.Error(), http.StatusInternalServerError)
314 }
315 issueTracker := session.IssueTracker
316 invalidLogin := "Invalid login credentials"
317 params, err := url.ParseQuery(r.URL.RawQuery)
318 if err != nil {
319 reportError(w, invalidLogin + ": " + err.Error(), http.StatusFor bidden)
320 return
321 }
322 code, ok := params["code"]
323 if !ok {
324 reportError(w, invalidLogin + ": redirect did not include auth c ode.",
325 http.StatusForbidden)
326 return
327 }
328 log.Println("Upgrading auth token:", code[0])
329 if err := issueTracker.UpgradeCode(code[0]); err != nil {
330 errMsg := "failed to upgrade token: " + err.Error()
331 reportError(w, errMsg, http.StatusForbidden)
332 return
333 }
334 if err := saveSession(session, w, r); err != nil {
335 reportError(w, "failed to save session: "+ err.Error(),
336 http.StatusInternalServerError)
337 return
338 }
339 http.Redirect(w, r, session.OrigRequestURL, http.StatusTemporaryRedirect )
340 return
341 }
342
343 // handleRoot is the handler function for all HTTP requests at the root level.
344 func handleRoot(w http.ResponseWriter, r *http.Request) {
345 log.Println("Fetching " + r.URL.Path)
346 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
347 handleBugChomper(w, r)
348 return
349 }
350 http.NotFound(w, r)
351 }
352
353 // Run the BugChomper server.
354 func main() {
355 var public bool
356 flag.BoolVar(
357 &public, "public", false, "Make this server publicly accessible. ")
358 flag.Parse()
359
360 http.HandleFunc("/", handleRoot)
361 http.HandleFunc(oauthCallbackPath, handleOAuth2Callback)
362 http.Handle("/res/", http.FileServer(http.Dir(curdir)))
363 port := ":" + strconv.Itoa(defaultPort)
364 log.Println("Server is running at " + scheme + "://" + localHost + port)
365 var err error
366 if public {
367 log.Println("WARNING: This server is not secure and should not b e made " +
368 "publicly accessible.")
369 scheme = "https"
370 err = http.ListenAndServeTLS(port, certFile, keyFile, nil)
371 } else {
372 scheme = "http"
373 err = http.ListenAndServe(localHost+port, nil)
374 }
375 if err != nil {
376 log.Println(err.Error())
377 }
378 }
OLDNEW
« no previous file with comments | « tools/bug_chomper/src/issue_tracker/issue_tracker.go ('k') | tools/bug_chomper/templates/bug_chomper.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698