blob: 725aa45c0329a31430a9735e7068ba935f4e91cc [file] [log] [blame]
// Copyright 2018 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package dash
import (
"errors"
"fmt"
"net/http"
"strings"
"golang.org/x/net/context"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
"google.golang.org/appengine/log"
"google.golang.org/appengine/user"
)
type AccessLevel int
const (
AccessPublic AccessLevel = iota + 1
AccessUser
AccessAdmin
)
func verifyAccessLevel(access AccessLevel) {
switch access {
case AccessPublic, AccessUser, AccessAdmin:
return
default:
panic(fmt.Sprintf("bad access level %v", access))
}
}
var ErrAccess = errors.New("unauthorized")
func checkAccessLevel(c context.Context, r *http.Request, level AccessLevel) error {
if accessLevel(c, r) >= level {
return nil
}
if u := user.Current(c); u != nil {
// Log only if user is signed in. Not-signed-in users are redirected to login page.
log.Errorf(c, "unauthorized access: %q [%q] access level %v", u.Email, u.AuthDomain, level)
}
return ErrAccess
}
func accessLevel(c context.Context, r *http.Request) AccessLevel {
if user.IsAdmin(c) {
switch r.FormValue("access") {
case "public":
return AccessPublic
case "user":
return AccessUser
}
return AccessAdmin
}
u := user.Current(c)
if u == nil ||
// devappserver is broken
u.AuthDomain != "gmail.com" && !appengine.IsDevAppServer() ||
!strings.HasSuffix(u.Email, config.AuthDomain) {
return AccessPublic
}
return AccessUser
}
func checkTextAccess(c context.Context, r *http.Request, tag string, id int64) (*Crash, error) {
switch tag {
default:
return nil, checkAccessLevel(c, r, AccessAdmin)
case textPatch:
return nil, checkJobTextAccess(c, r, "Patch", id)
case textError:
return nil, checkJobTextAccess(c, r, "Error", id)
case textKernelConfig:
// This is checked based on text namespace.
return nil, nil
case textCrashLog:
// Log and Report can be attached to a Crash or Job.
crash, err := checkCrashTextAccess(c, r, "Log", id)
if err == nil || err == ErrAccess {
return crash, err
}
return nil, checkJobTextAccess(c, r, "CrashLog", id)
case textCrashReport:
crash, err := checkCrashTextAccess(c, r, "Report", id)
if err == nil || err == ErrAccess {
return crash, err
}
return nil, checkJobTextAccess(c, r, "CrashReport", id)
case textReproSyz:
return checkCrashTextAccess(c, r, "ReproSyz", id)
case textReproC:
return checkCrashTextAccess(c, r, "ReproC", id)
}
}
func checkCrashTextAccess(c context.Context, r *http.Request, field string, id int64) (*Crash, error) {
var crashes []*Crash
keys, err := datastore.NewQuery("Crash").
Filter(field+"=", id).
GetAll(c, &crashes)
if err != nil {
return nil, fmt.Errorf("failed to query crashes: %v", err)
}
if len(crashes) != 1 {
return nil, fmt.Errorf("checkCrashTextAccess: found %v crashes for %v=%v",
len(crashes), field, id)
}
crash := crashes[0]
bug := new(Bug)
if err := datastore.Get(c, keys[0].Parent(), bug); err != nil {
return nil, fmt.Errorf("failed to get bug: %v", err)
}
bugLevel := bug.sanitizeAccess(accessLevel(c, r))
return crash, checkAccessLevel(c, r, bugLevel)
}
func checkJobTextAccess(c context.Context, r *http.Request, field string, id int64) error {
keys, err := datastore.NewQuery("Job").
Filter(field+"=", id).
KeysOnly().
GetAll(c, nil)
if err != nil {
return fmt.Errorf("failed to query jobs: %v", err)
}
if len(keys) != 1 {
return fmt.Errorf("checkJobTextAccess: found %v jobs for %v=%v",
len(keys), field, id)
}
bug := new(Bug)
if err := datastore.Get(c, keys[0].Parent(), bug); err != nil {
return fmt.Errorf("failed to get bug: %v", err)
}
bugLevel := bug.sanitizeAccess(accessLevel(c, r))
return checkAccessLevel(c, r, bugLevel)
}
func (bug *Bug) sanitizeAccess(currentLevel AccessLevel) AccessLevel {
for ri := len(bug.Reporting) - 1; ri >= 0; ri-- {
bugReporting := &bug.Reporting[ri]
if ri == 0 || !bugReporting.Reported.IsZero() {
ns := config.Namespaces[bug.Namespace]
bugLevel := ns.ReportingByName(bugReporting.Name).AccessLevel
if currentLevel < bugLevel {
if bug.Status == BugStatusFixed || len(bug.Commits) != 0 {
// Fixed bugs are visible in all reportings,
// however, without previous reporting private information.
lastLevel := ns.Reporting[len(ns.Reporting)-1].AccessLevel
if currentLevel >= lastLevel {
bugLevel = lastLevel
sanitizeReporting(bug)
}
}
}
return bugLevel
}
}
panic("unreachable")
}
func sanitizeReporting(bug *Bug) {
bug.DupOf = ""
for ri := range bug.Reporting {
bugReporting := &bug.Reporting[ri]
bugReporting.ID = ""
bugReporting.ExtID = ""
bugReporting.Link = ""
}
}