Collect the username when data scouting data is submitted

This patch adds the username of the person that is submitting scouting
data.

I kind of amended the existing unit test to validate this feature by
injecting a fake username at the right places. It doesn't validate
actual HTTPS traffic, but it's good enough for now.

Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I483cf30fd046965b23916b129a074906b586b096
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 245891a..2076030 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -1,9 +1,11 @@
 package requests
 
 import (
+	"encoding/base64"
 	"errors"
 	"fmt"
 	"io"
+	"log"
 	"net/http"
 	"strconv"
 	"strings"
@@ -80,12 +82,44 @@
 	return result, success
 }
 
+// Parses the authorization information that the browser inserts into the
+// headers.  The authorization follows this format:
+//
+//  req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
+func parseUsername(req *http.Request) string {
+	auth, ok := req.Header["Authorization"]
+	if !ok {
+		return "unknown"
+	}
+
+	parts := strings.Split(auth[0], " ")
+	if !(len(parts) == 2 && parts[0] == "Basic") {
+		return "unknown"
+	}
+
+	info, err := base64.StdEncoding.DecodeString(parts[1])
+	if err != nil {
+		log.Println("ERROR: Failed to parse Basic authentication.")
+		return "unknown"
+	}
+
+	loginParts := strings.Split(string(info), ":")
+	if len(loginParts) != 2 {
+		return "unknown"
+	}
+	return loginParts[0]
+}
+
 // Handles a SubmitDataScouting request.
 type submitDataScoutingHandler struct {
 	db Database
 }
 
 func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	// Get the username of the person submitting the data.
+	username := parseUsername(req)
+	log.Println("Got data scouting data from", username)
+
 	requestBytes, err := io.ReadAll(req.Body)
 	if err != nil {
 		respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
@@ -108,6 +142,7 @@
 		LowerGoalShots:  request.LowerGoalTele(),
 		PlayedDefense:   request.DefenseRating(),
 		Climbing:        request.Climbing(),
+		CollectedBy:     username,
 	}
 
 	err = handler.db.AddToStats(stats)
@@ -152,7 +187,7 @@
 
 	matches, err := handler.db.ReturnMatches()
 	if err != nil {
-		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
+		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
 		return
 	}
 
@@ -281,6 +316,7 @@
 			LowerGoalTele:   stat.LowerGoalShots,
 			DefenseRating:   stat.PlayedDefense,
 			Climbing:        stat.Climbing,
+			CollectedBy:     stat.CollectedBy,
 		})
 	}