You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ne...@apache.org on 2018/10/17 19:44:50 UTC

[trafficcontrol] 04/08: TO Go move getting user to func, for plugin use

This is an automated email from the ASF dual-hosted git repository.

neuman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 9d3361316d5298cdebaabf7902b6ea8b3b3a1121
Author: Robert Butts <ro...@apache.org>
AuthorDate: Tue Sep 25 16:05:45 2018 -0600

    TO Go move getting user to func, for plugin use
---
 traffic_ops/traffic_ops_golang/api/api.go          | 69 ++++++++++++++++--
 traffic_ops/traffic_ops_golang/plugin/plugin.go    |  7 +-
 traffic_ops/traffic_ops_golang/routing.go          | 19 +++--
 .../traffic_ops_golang/traffic_ops_golang.go       |  2 +-
 traffic_ops/traffic_ops_golang/wrappers.go         | 81 ++--------------------
 5 files changed, 87 insertions(+), 91 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/api/api.go b/traffic_ops/traffic_ops_golang/api/api.go
index 6d3d5d0..d46df5f 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -37,6 +37,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tocookie"
 
 	"github.com/jmoiron/sqlx"
 	"github.com/lib/pq"
@@ -264,7 +265,7 @@ type APIInfo struct {
 //
 // It is encouraged to call APIInfo.Tx.Tx.Commit() manually when all queries are finished, to release database resources early, and also to return an error to the user if the commit failed.
 //
-// NewInfo guarantees the returned APIInfo.Tx is nil or valid, even if a returned error is not nil. Hence, it is safe to pass the Tx to HandleErr when this returns errors.
+// NewInfo guarantees the returned APIInfo.Tx is non-nil and APIInfo.Tx.Tx is nil or valid, even if a returned error is not nil. Hence, it is safe to pass the Tx.Tx to HandleErr when this returns errors.
 //
 // Close() must be called to free resources, and should be called in a defer immediately after NewInfo(), to finish the transaction.
 //
@@ -292,29 +293,29 @@ type APIInfo struct {
 func NewInfo(r *http.Request, requiredParams []string, intParamNames []string) (*APIInfo, error, error, int) {
 	db, err := getDB(r.Context())
 	if err != nil {
-		return &APIInfo{}, errors.New("getting db: " + err.Error()), nil, http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting db: " + err.Error()), nil, http.StatusInternalServerError
 	}
 	cfg, err := getConfig(r.Context())
 	if err != nil {
-		return &APIInfo{}, errors.New("getting config: " + err.Error()), nil, http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting config: " + err.Error()), nil, http.StatusInternalServerError
 	}
 	reqID, err := getReqID(r.Context())
 	if err != nil {
-		return &APIInfo{}, errors.New("getting reqID: " + err.Error()), nil, http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting reqID: " + err.Error()), nil, http.StatusInternalServerError
 	}
 
 	user, err := auth.GetCurrentUser(r.Context())
 	if err != nil {
-		return &APIInfo{}, errors.New("getting user: " + err.Error()), nil, http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting user: " + err.Error()), nil, http.StatusInternalServerError
 	}
 	params, intParams, userErr, sysErr, errCode := AllParams(r, requiredParams, intParamNames)
 	if userErr != nil || sysErr != nil {
-		return &APIInfo{}, userErr, sysErr, errCode
+		return &APIInfo{Tx: &sqlx.Tx{}}, userErr, sysErr, errCode
 	}
 	dbCtx, _ := context.WithTimeout(r.Context(), time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second) //only place we could call cancel here is in APIInfo.Close(), which already will rollback the transaction (which is all cancel will do.)
 	tx, err := db.BeginTxx(dbCtx, nil)                                                                 // must be last, MUST not return an error if this succeeds, without closing the tx
 	if err != nil {
-		return &APIInfo{}, userErr, errors.New("could not begin transaction: " + err.Error()), http.StatusInternalServerError
+		return &APIInfo{Tx: &sqlx.Tx{}}, userErr, errors.New("could not begin transaction: " + err.Error()), http.StatusInternalServerError
 	}
 	return &APIInfo{
 		Config:    cfg,
@@ -479,3 +480,57 @@ func ParseDBError(ierr error) (error, error, int) {
 
 	return nil, err, http.StatusInternalServerError
 }
+
+// GetUserFromReq returns the current user, any user error, any system error, and an error code to be returned if either error was not nil.
+// This also uses the given ResponseWriter to refresh the cookie, if it was valid.
+func GetUserFromReq(w http.ResponseWriter, r *http.Request, secret string) (auth.CurrentUser, error, error, int) {
+	cookie, err := r.Cookie(tocookie.Name)
+	if err != nil {
+		return auth.CurrentUser{}, errors.New("Unauthorized, please log in."), errors.New("error getting cookie: " + err.Error()), http.StatusUnauthorized
+	}
+
+	if cookie == nil {
+		return auth.CurrentUser{}, errors.New("Unauthorized, please log in."), nil, http.StatusUnauthorized
+	}
+
+	oldCookie, err := tocookie.Parse(secret, cookie.Value)
+	if err != nil {
+		return auth.CurrentUser{}, errors.New("Unauthorized, please log in."), errors.New("error parsing cookie: " + err.Error()), http.StatusUnauthorized
+	}
+
+	username := oldCookie.AuthData
+	if username == "" {
+		return auth.CurrentUser{}, errors.New("Unauthorized, please log in."), nil, http.StatusUnauthorized
+	}
+	db := (*sqlx.DB)(nil)
+	val := r.Context().Value(DBContextKey)
+	if val == nil {
+		return auth.CurrentUser{}, nil, errors.New("request context db missing"), http.StatusInternalServerError
+	}
+	switch v := val.(type) {
+	case *sqlx.DB:
+		db = v
+	default:
+		return auth.CurrentUser{}, nil, fmt.Errorf("request context db unknown type %T", val), http.StatusInternalServerError
+	}
+
+	cfg, err := GetConfig(r.Context())
+	if err != nil {
+		return auth.CurrentUser{}, nil, errors.New("request context config missing"), http.StatusInternalServerError
+	}
+
+	user, userErr, sysErr, code := auth.GetCurrentUserFromDB(db, username, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second)
+	if userErr != nil || sysErr != nil {
+		return auth.CurrentUser{}, userErr, sysErr, code
+	}
+
+	newCookieVal := tocookie.Refresh(oldCookie, secret)
+	http.SetCookie(w, &http.Cookie{Name: tocookie.Name, Value: newCookieVal, Path: "/", HttpOnly: true})
+	return user, nil, nil, http.StatusOK
+}
+
+func AddUserToReq(r *http.Request, u auth.CurrentUser) *http.Request {
+	ctx := r.Context()
+	ctx = context.WithValue(ctx, auth.CurrentUserKey, u)
+	return r.WithContext(ctx)
+}
diff --git a/traffic_ops/traffic_ops_golang/plugin/plugin.go b/traffic_ops/traffic_ops_golang/plugin/plugin.go
index 2c2ab79..998303f 100644
--- a/traffic_ops/traffic_ops_golang/plugin/plugin.go
+++ b/traffic_ops/traffic_ops_golang/plugin/plugin.go
@@ -97,7 +97,9 @@ func AddPlugin(priority uint64, funcs Funcs) {
 		fmt.Println(time.Now().Format(time.RFC3339Nano) + " Error plugin.AddPlugin: runtime.Caller failed, can't get plugin names") // print, because this is called in init, loggers don't exist yet
 		os.Exit(1)
 	}
+
 	pluginName := strings.TrimSuffix(path.Base(filename), ".go")
+	log.Debugln("AddPlugin adding " + pluginName)
 	initPlugins = append(initPlugins, pluginObj{funcs: funcs, priority: priority, name: pluginName})
 }
 
@@ -113,11 +115,11 @@ type Data struct {
 	Ctx       *interface{}
 	SharedCfg map[string]interface{}
 	RequestID uint64
+	AppCfg    config.Config
 }
 
 type StartupData struct {
 	Data
-	AppCfg config.Config
 }
 
 type OnRequestData struct {
@@ -173,12 +175,15 @@ func (ps plugins) OnStartup(d StartupData) {
 
 // OnRequest returns a boolean whether to immediately stop processing the request. If a plugin returns true, this is immediately returned with no further plugins processed.
 func (ps plugins) OnRequest(d OnRequestData) bool {
+	log.Debugln("DEBUG plugins.OnRequest calling %+v\n", len(ps.slice))
 	for _, p := range ps.slice {
 		if p.funcs.onRequest == nil {
+			log.Debugln("plugins.OnRequest plugging " + p.name + " - no onRequest func")
 			continue
 		}
 		d.Ctx = ps.ctx[p.name]
 		d.Cfg = ps.cfg[p.name]
+		log.Debugln("plugins.OnRequest plugging " + p.name)
 		if stop := p.funcs.onRequest(d); stop {
 			return true
 		}
diff --git a/traffic_ops/traffic_ops_golang/routing.go b/traffic_ops/traffic_ops_golang/routing.go
index 8a19861..a6a75ce 100644
--- a/traffic_ops/traffic_ops_golang/routing.go
+++ b/traffic_ops/traffic_ops_golang/routing.go
@@ -191,7 +191,16 @@ func Handler(
 		log.Infoln(r.Method + " " + r.URL.Path + " handled (reqid " + reqIDStr + ") in " + time.Since(start).String())
 	}()
 
-	onReqData := plugin.OnRequestData{Data: plugin.Data{RequestID: reqID}, W: w, R: r}
+	ctx := r.Context()
+	ctx = context.WithValue(ctx, api.DBContextKey, db)
+	ctx = context.WithValue(ctx, api.ConfigContextKey, cfg)
+	ctx = context.WithValue(ctx, api.ReqIDContextKey, reqID)
+
+	// plugins have no pre-parsed path params, but add an empty map so they can use the api helper funcs that require it.
+	pluginCtx := context.WithValue(ctx, api.PathParamsKey, map[string]string{})
+	pluginReq := r.WithContext(pluginCtx)
+
+	onReqData := plugin.OnRequestData{Data: plugin.Data{RequestID: reqID, AppCfg: *cfg}, W: w, R: pluginReq}
 	if handled := plugins.OnRequest(onReqData); handled {
 		return
 	}
@@ -213,12 +222,8 @@ func Handler(
 			params[v] = match[i+1]
 		}
 
-		ctx := r.Context()
-		ctx = context.WithValue(ctx, api.PathParamsKey, params)
-		ctx = context.WithValue(ctx, api.DBContextKey, db)
-		ctx = context.WithValue(ctx, api.ConfigContextKey, cfg)
-		ctx = context.WithValue(ctx, api.ReqIDContextKey, reqID)
-		r = r.WithContext(ctx)
+		routeCtx := context.WithValue(ctx, api.PathParamsKey, params)
+		r = r.WithContext(routeCtx)
 		compiledRoute.Handler(w, r)
 		return
 	}
diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
index 1941293..3fad34d 100644
--- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
+++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
@@ -160,7 +160,7 @@ func main() {
 		return
 	}
 
-	plugins.OnStartup(plugin.StartupData{Data: plugin.Data{SharedCfg: cfg.PluginSharedConfig}, AppCfg: cfg})
+	plugins.OnStartup(plugin.StartupData{Data: plugin.Data{SharedCfg: cfg.PluginSharedConfig, AppCfg: cfg}})
 
 	log.Infof("Listening on " + cfg.Port)
 
diff --git a/traffic_ops/traffic_ops_golang/wrappers.go b/traffic_ops/traffic_ops_golang/wrappers.go
index 914f706..6d3b0d9 100644
--- a/traffic_ops/traffic_ops_golang/wrappers.go
+++ b/traffic_ops/traffic_ops_golang/wrappers.go
@@ -22,10 +22,8 @@ package main
 import (
 	"bytes"
 	"compress/gzip"
-	"context"
 	"crypto/sha512"
 	"encoding/base64"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
@@ -38,9 +36,7 @@ import (
 	tc "github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/about"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tocookie"
-	"github.com/jmoiron/sqlx"
 )
 
 // ServerName - the server identifier
@@ -59,81 +55,16 @@ func (a AuthBase) GetWrapper(privLevelRequired int) Middleware {
 	}
 	return func(handlerFunc http.HandlerFunc) http.HandlerFunc {
 		return func(w http.ResponseWriter, r *http.Request) {
-			// TODO remove, and make username available to wrapLogTime
-			iw := &Interceptor{w: w}
-			w = iw
-			handleErr := func(status int, err error) {
-				errBytes, jsonErr := json.Marshal(tc.CreateErrorAlerts(err))
-				if jsonErr != nil {
-					log.Errorf("failed to marshal error: %s\n", jsonErr)
-					w.WriteHeader(http.StatusInternalServerError)
-					fmt.Fprintf(w, http.StatusText(http.StatusInternalServerError))
-					return
-				}
-				w.Header().Set(tc.ContentType, tc.ApplicationJson)
-				w.WriteHeader(status)
-				fmt.Fprintf(w, "%s", errBytes)
-			}
-
-			cookie, err := r.Cookie(tocookie.Name)
-			if err != nil {
-				log.Errorf("error getting cookie: %s", err)
-				handleErr(http.StatusUnauthorized, errors.New("Unauthorized, please log in."))
-				return
-			}
-
-			if cookie == nil {
-				handleErr(http.StatusUnauthorized, errors.New("Unauthorized, please log in."))
-				return
-			}
-
-			oldCookie, err := tocookie.Parse(a.secret, cookie.Value)
-			if err != nil {
-				log.Errorf("error parsing cookie: %s", err)
-				handleErr(http.StatusUnauthorized, errors.New("Unauthorized, please log in."))
-				return
-			}
-
-			username := oldCookie.AuthData
-			if username == "" {
-				handleErr(http.StatusUnauthorized, errors.New("Unauthorized, please log in."))
-				return
-			}
-			var DB *sqlx.DB
-			val := r.Context().Value(api.DBContextKey)
-			if val != nil {
-				switch v := val.(type) {
-				case *sqlx.DB:
-					DB = v
-				default:
-					handleErr(http.StatusInternalServerError, errors.New("No DB found"))
-				}
-			} else {
-				handleErr(http.StatusInternalServerError, errors.New("No DB found"))
-			}
-
-			cfg, err := api.GetConfig(r.Context())
-			if err != nil {
-				handleErr(http.StatusInternalServerError, errors.New("No config found"))
-			}
-
-			currentUserInfo, userErr, sysErr, code := auth.GetCurrentUserFromDB(DB, username, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second)
+			user, userErr, sysErr, errCode := api.GetUserFromReq(w, r, a.secret)
 			if userErr != nil || sysErr != nil {
-				api.HandleErr(w, r, nil, code, userErr, sysErr)
+				api.HandleErr(w, r, nil, errCode, userErr, sysErr)
 				return
 			}
-			if currentUserInfo.PrivLevel < privLevelRequired {
-				handleErr(http.StatusForbidden, errors.New("Forbidden."))
-				return
+			if user.PrivLevel < privLevelRequired {
+				api.HandleErr(w, r, nil, http.StatusForbidden, errors.New("Forbidden."), nil)
 			}
-
-			newCookieVal := tocookie.Refresh(oldCookie, a.secret)
-			http.SetCookie(w, &http.Cookie{Name: tocookie.Name, Value: newCookieVal, Path: "/", HttpOnly: true})
-
-			ctx := r.Context()
-			ctx = context.WithValue(ctx, auth.CurrentUserKey, currentUserInfo)
-
-			handlerFunc(w, r.WithContext(ctx))
+			r = api.AddUserToReq(r, user)
+			handlerFunc(w, r)
 		}
 	}
 }