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/debug/cli/cli_test.py b/scouting/webserver/requests/debug/cli/cli_test.py
index f4b82b4..b20703b 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -91,7 +91,8 @@
UpperGoalTele: (int32) 14,
LowerGoalTele: (int32) 15,
DefenseRating: (int32) 3,
- Climbing: (int32) 1
+ Climbing: (int32) 1,
+ CollectedBy: (string) (len=9) "debug_cli"
}"""), stdout)
def test_request_all_matches(self):
diff --git a/scouting/webserver/requests/debug/debug.go b/scouting/webserver/requests/debug/debug.go
index 81be3d1..49b6f79 100644
--- a/scouting/webserver/requests/debug/debug.go
+++ b/scouting/webserver/requests/debug/debug.go
@@ -2,6 +2,7 @@
import (
"bytes"
+ "encoding/base64"
"errors"
"fmt"
"io"
@@ -16,6 +17,9 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
)
+// The username to submit the various requests as.
+const DefaultUsername = "debug_cli"
+
// Use aliases to make the rest of the code more readable.
type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
@@ -66,7 +70,16 @@
// Performs a POST request with the specified payload. The bytes that the
// server responds with are returned.
func performPost(url string, requestBytes []byte) ([]byte, error) {
- resp, err := http.Post(url, "application/octet-stream", bytes.NewReader(requestBytes))
+ req, err := http.NewRequest("POST", url, bytes.NewReader(requestBytes))
+ if err != nil {
+ log.Printf("Failed to create a new POST request to %s: %v", url, err)
+ return nil, err
+ }
+ req.Header.Add("Authorization", "Basic "+
+ base64.StdEncoding.EncodeToString([]byte(DefaultUsername+":")))
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
if err != nil {
log.Printf("Failed to send POST request to %s: %v", url, err)
return nil, err
diff --git a/scouting/webserver/requests/messages/request_data_scouting_response.fbs b/scouting/webserver/requests/messages/request_data_scouting_response.fbs
index e30ab42..5703a07 100644
--- a/scouting/webserver/requests/messages/request_data_scouting_response.fbs
+++ b/scouting/webserver/requests/messages/request_data_scouting_response.fbs
@@ -11,6 +11,7 @@
lower_goal_tele:int (id:7);
defense_rating:int (id:8);
climbing:int (id:9);
+ collected_by:string (id:10);
}
table RequestDataScoutingResponse {
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,
})
}
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index 60bee0e..0183736 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -222,12 +222,14 @@
ShotsMissed: 1, UpperGoalShots: 2, LowerGoalShots: 3,
ShotsMissedAuto: 4, UpperGoalAuto: 5, LowerGoalAuto: 6,
PlayedDefense: 7, Climbing: 8,
+ CollectedBy: "john",
},
{
TeamNumber: 972, MatchNumber: 1,
ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
ShotsMissedAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
PlayedDefense: 8, Climbing: 9,
+ CollectedBy: "andrea",
},
},
}
@@ -250,17 +252,20 @@
// MissedShotsAuto, UpperGoalAuto, LowerGoalAuto,
// MissedShotsTele, UpperGoalTele, LowerGoalTele,
// DefenseRating, Climbing,
+ // CollectedBy,
{
971, 1,
4, 5, 6,
1, 2, 3,
7, 8,
+ "john",
},
{
972, 1,
5, 6, 7,
2, 3, 4,
8, 9,
+ "andrea",
},
},
}