From fa64d9dad4d4f7f851bed8824266e56c20e9769c Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Mon, 20 Mar 2023 14:49:58 +0100 Subject: [PATCH 01/10] Replace mux with gin router --- README.md | 4 +- api/api.go | 19 +++--- api/middleware/middleware.go | 64 +++++++++----------- api/middleware/middleware_test.go | 89 ++++++++++----------------- api/sda/sda.go | 52 +++++++--------- api/sda/sda_test.go | 99 +++++++++++++++++-------------- go.mod | 21 ++++++- go.sum | 52 ++++++++++++++-- 8 files changed, 210 insertions(+), 190 deletions(-) diff --git a/README.md b/README.md index be710f5..12a8928 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ There is an README file in the [dev_utils](/dev_utils) folder with sections for | Component | Role | |---------------|------| | middleware | Performs access token verification and validation | -| sda | Constructs the main API endpoints fort the NeIC SDA Data Out API. | +| sda | Constructs the main API endpoints for the NeIC SDA Data Out API. | ## Internal Components @@ -43,4 +43,4 @@ There is an README file in the [dev_utils](/dev_utils) folder with sections for | Component | Role | |---------------|------| | auth | Auth pkg is used by the middleware to parse OIDC Details and extract GA4GH Visas from a [GA4GH Passport](https://github.com/ga4gh-duri/ga4gh-duri.github.io/blob/master/researcher_ids/ga4gh_passport_v1.md) | -| request | This pkg Stores a HTTP client, so that it doesn't need to be initialised on every request. | \ No newline at end of file +| request | This pkg Stores a HTTP client, so that it doesn't need to be initialised on every request. | diff --git a/api/api.go b/api/api.go index 6fd934c..2d3b442 100644 --- a/api/api.go +++ b/api/api.go @@ -6,7 +6,7 @@ import ( "net/http" "time" - "github.com/gorilla/mux" + "github.com/gin-gonic/gin" "github.com/neicnordic/sda-download/api/middleware" "github.com/neicnordic/sda-download/api/sda" "github.com/neicnordic/sda-download/internal/config" @@ -14,21 +14,22 @@ import ( ) // healthResponse -func healthResponse(w http.ResponseWriter, r *http.Request) { +func healthResponse(c *gin.Context) { // ok response to health - w.WriteHeader(http.StatusOK) + c.Writer.WriteHeader(http.StatusOK) } // Setup configures the web server and registers the routes func Setup() *http.Server { // Set up routing log.Info("(2/5) Registering endpoint handlers") - r := mux.NewRouter().SkipClean(true) - r.Handle("/metadata/datasets", middleware.TokenMiddleware(http.HandlerFunc(sda.Datasets))).Methods("GET") - r.Handle("/metadata/datasets/{dataset:[^\\s/$.?#].[^\\s]+|[A-Za-z0-9-_:.]+}/files", middleware.TokenMiddleware(http.HandlerFunc(sda.Files))).Methods("GET") - r.Handle("/files/{fileid:[A-Za-z0-9-_:.]+}", middleware.TokenMiddleware(http.HandlerFunc(sda.Download))).Methods("GET") - r.HandleFunc("/health", healthResponse).Methods("GET") + router := gin.Default() + + router.GET("/metadata/datasets", middleware.TokenMiddleware(), sda.Datasets) + router.GET("/metadata/datasets/:dataset/files", middleware.TokenMiddleware(), sda.Files) + router.GET("/files/:fileid", middleware.TokenMiddleware(), sda.Download) + router.GET("/health", healthResponse) // Configure TLS settings log.Info("(3/5) Configuring TLS") @@ -45,7 +46,7 @@ func Setup() *http.Server { log.Info("(4/5) Configuring server") srv := &http.Server{ Addr: config.Config.App.Host + ":" + fmt.Sprint(config.Config.App.Port), - Handler: r, + Handler: router, TLSConfig: cfg, TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), ReadHeaderTimeout: 20 * time.Second, diff --git a/api/middleware/middleware.go b/api/middleware/middleware.go index 017560a..304754d 100644 --- a/api/middleware/middleware.go +++ b/api/middleware/middleware.go @@ -1,46 +1,43 @@ package middleware import ( - "context" "net/http" + "github.com/gin-gonic/gin" "github.com/neicnordic/sda-download/internal/config" "github.com/neicnordic/sda-download/internal/session" "github.com/neicnordic/sda-download/pkg/auth" log "github.com/sirupsen/logrus" ) -type stringVariable string - // as specified in docs: https://pkg.go.dev/context#WithValue -var datasetsKey = stringVariable("datasets") +var datasetsKey = "datasets" // TokenMiddleware performs access token verification and validation // JWTs are verified and validated by the app, opaque tokens are sent to AAI for verification // Successful auth results in list of authorised datasets -func TokenMiddleware(nextHandler http.Handler) http.Handler { - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +func TokenMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { // Check if dataset permissions are cached to session - sessionCookie, err := r.Cookie(config.Config.Session.Name) + sessionCookie, err := c.Cookie(config.Config.Session.Name) if err != nil { log.Debugf("no session cookie received") } var datasets []string var exists bool - if sessionCookie != nil { + if sessionCookie != "" { log.Debug("session cookie received") - datasets, exists = session.Get(sessionCookie.Value) + datasets, exists = session.Get(sessionCookie) } if !exists { log.Debug("no session found, create new session") // Check that a token is provided - token, code, err := auth.GetToken(r.Header.Get("Authorization")) + token, code, err := auth.GetToken(c.Request.Header.Get("Authorization")) if err != nil { - http.Error(w, err.Error(), code) + c.String(code, err.Error()) return } @@ -49,7 +46,7 @@ func TokenMiddleware(nextHandler http.Handler) http.Handler { visas, err := auth.GetVisas(auth.Details, token) if err != nil { log.Debug("failed to validate token at AAI") - http.Error(w, "bad token", 401) + c.String(http.StatusUnauthorized, "bad token") return } @@ -64,47 +61,40 @@ func TokenMiddleware(nextHandler http.Handler) http.Handler { // Start a new session and store datasets under the session key key := session.NewSessionKey() session.Set(key, datasets) - sessionCookie := &http.Cookie{ - Name: config.Config.Session.Name, - Value: key, - Domain: config.Config.Session.Domain, - Secure: config.Config.Session.Secure, - HttpOnly: config.Config.Session.HTTPOnly, - // time.Duration is stored in nanoseconds, but MaxAge wants seconds - MaxAge: int(config.Config.Session.Expiration) / 1e9, - } - http.SetCookie(w, sessionCookie) + c.SetCookie(config.Config.Session.Name, // name + key, // value + int(config.Config.Session.Expiration)/1e9, // max age + "/", // path + config.Config.Session.Domain, // domain + config.Config.Session.Secure, // secure + config.Config.Session.HTTPOnly, // httpOnly + ) log.Debug("authorization check passed") } // Store dataset list to request context, for use in the endpoint handlers - modifiedContext := storeDatasets(r.Context(), datasets) - modifiedRequest := r.WithContext(modifiedContext) + storeDatasets(c, datasets) // Forward request to the next endpoint handler - nextHandler.ServeHTTP(w, modifiedRequest) - }) + c.Next() + } } // storeDatasets stores the dataset list to the request context -func storeDatasets(ctx context.Context, datasets []string) context.Context { +func storeDatasets(c *gin.Context, datasets []string) *gin.Context { log.Debugf("storing %v datasets to request context", datasets) - ctx = context.WithValue(ctx, datasetsKey, datasets) + c.Set(datasetsKey, datasets) - return ctx + return c } // GetDatasets extracts the dataset list from the request context -var GetDatasets = func(ctx context.Context) []string { - datasets := ctx.Value(datasetsKey) - if datasets == nil { - log.Debug("request datasets context is empty") +var GetDatasets = func(c *gin.Context) []string { + datasets := c.GetStringSlice(datasetsKey) - return []string{} - } log.Debugf("returning %v from request context", datasets) - return datasets.([]string) + return datasets } diff --git a/api/middleware/middleware_test.go b/api/middleware/middleware_test.go index 7233f33..fa00746 100644 --- a/api/middleware/middleware_test.go +++ b/api/middleware/middleware_test.go @@ -9,16 +9,18 @@ import ( "reflect" "testing" + "github.com/gin-gonic/gin" "github.com/neicnordic/sda-download/internal/config" "github.com/neicnordic/sda-download/internal/session" "github.com/neicnordic/sda-download/pkg/auth" + log "github.com/sirupsen/logrus" ) const token string = "token" // testEndpoint mimics the endpoint handlers that perform business logic after passing the // authentication middleware. This handler is generic and can be used for all cases. -func testEndpoint(w http.ResponseWriter, r *http.Request) {} +func testEndpoint(c *gin.Context) {} func TestTokenMiddleware_Fail_GetToken(t *testing.T) { @@ -32,18 +34,19 @@ func TestTokenMiddleware_Fail_GetToken(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + r := httptest.NewRequest("GET", "/", nil) + _, router := gin.CreateTestContext(w) // Send a request through the middleware - testHandler := TokenMiddleware(http.HandlerFunc(testEndpoint)) - testHandler.ServeHTTP(w, r) + router.GET("/", TokenMiddleware(), testEndpoint) + router.ServeHTTP(w, r) // Test the outcomes of the handler response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 401 - expectedBody := []byte("access token must be provided\n") + expectedBody := []byte("access token must be provided") if response.StatusCode != expectedStatusCode { t.Errorf("TestTokenMiddleware_Fail_GetToken failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -76,18 +79,19 @@ func TestTokenMiddleware_Fail_GetVisas(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + r := httptest.NewRequest("GET", "/", nil) + _, router := gin.CreateTestContext(w) // Send a request through the middleware - testHandler := TokenMiddleware(http.HandlerFunc(testEndpoint)) - testHandler.ServeHTTP(w, r) + router.GET("/", TokenMiddleware(), testEndpoint) + router.ServeHTTP(w, r) // Test the outcomes of the handler response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 401 - expectedBody := []byte("bad token\n") + expectedBody := []byte("bad token") if response.StatusCode != expectedStatusCode { t.Errorf("TestTokenMiddleware_Fail_GetVisas failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -125,11 +129,12 @@ func TestTokenMiddleware_Fail_GetPermissions(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + r := httptest.NewRequest("GET", "/", nil) + _, router := gin.CreateTestContext(w) // Send a request through the middleware - testHandler := TokenMiddleware(http.HandlerFunc(testEndpoint)) - testHandler.ServeHTTP(w, r) + router.GET("/", TokenMiddleware(), testEndpoint) + router.ServeHTTP(w, r) // Test the outcomes of the handler response := w.Result() @@ -171,20 +176,21 @@ func TestTokenMiddleware_Success_NoCache(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + r := httptest.NewRequest("GET", "/", nil) + _, router := gin.CreateTestContext(w) // Now that we are modifying the request context, we need to place the context test inside the handler expectedDatasets := []string{"dataset1", "dataset2"} - testEndpointWithContextData := func(w http.ResponseWriter, r *http.Request) { - datasets := r.Context().Value(datasetsKey).([]string) + testEndpointWithContextData := func(c *gin.Context) { + datasets := c.GetStringSlice(datasetsKey) if !reflect.DeepEqual(datasets, expectedDatasets) { t.Errorf("TestTokenMiddleware_Success_NoCache failed, got %s expected %s", datasets, expectedDatasets) } } // Send a request through the middleware - testHandler := TokenMiddleware(http.HandlerFunc(testEndpointWithContextData)) - testHandler.ServeHTTP(w, r) + router.GET("/", TokenMiddleware(), testEndpointWithContextData) + router.ServeHTTP(w, r) // Test the outcomes of the handler response := w.Result() @@ -219,6 +225,8 @@ func TestTokenMiddleware_Success_FromCache(t *testing.T) { // Substitute mock functions session.Get = func(key string) ([]string, bool) { + log.Warningf("session.Get %v", key) + return []string{"dataset1", "dataset2"}, true } @@ -226,7 +234,9 @@ func TestTokenMiddleware_Success_FromCache(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + r := httptest.NewRequest("GET", "/", nil) + _, router := gin.CreateTestContext(w) + r.AddCookie(&http.Cookie{ Name: "sda_session_key", Value: "key", @@ -234,16 +244,16 @@ func TestTokenMiddleware_Success_FromCache(t *testing.T) { // Now that we are modifying the request context, we need to place the context test inside the handler expectedDatasets := []string{"dataset1", "dataset2"} - testEndpointWithContextData := func(w http.ResponseWriter, r *http.Request) { - datasets := r.Context().Value(datasetsKey).([]string) + testEndpointWithContextData := func(c *gin.Context) { + datasets := c.GetStringSlice(datasetsKey) if !reflect.DeepEqual(datasets, expectedDatasets) { t.Errorf("TestTokenMiddleware_Success_FromCache failed, got %s expected %s", datasets, expectedDatasets) } } // Send a request through the middleware - testHandler := TokenMiddleware(http.HandlerFunc(testEndpointWithContextData)) - testHandler.ServeHTTP(w, r) + router.GET("/", TokenMiddleware(), testEndpointWithContextData) + router.ServeHTTP(w, r) // Test the outcomes of the handler response := w.Result() @@ -264,38 +274,3 @@ func TestTokenMiddleware_Success_FromCache(t *testing.T) { session.Get = originalGetCache } - -func TestStoreDatasets(t *testing.T) { - - // Get a request context for testing if data is saved - r := httptest.NewRequest("GET", "https://testing.fi", nil) - - // Store data to request context - datasets := []string{"dataset1", "dataset2"} - modifiedContext := storeDatasets(r.Context(), datasets) - - // Verify that context has new data - storedDatasets := modifiedContext.Value(datasetsKey).([]string) - if !reflect.DeepEqual(datasets, storedDatasets) { - t.Errorf("TestStoreDatasets failed, got %s, expected %s", storedDatasets, datasets) - } - -} - -func TestGetDatasets(t *testing.T) { - - // Get a request context for testing if data is saved - r := httptest.NewRequest("GET", "https://testing.fi", nil) - - // Store data to request context - datasets := []string{"dataset1", "dataset2"} - modifiedContext := storeDatasets(r.Context(), datasets) - modifiedRequest := r.WithContext(modifiedContext) - - // Verify that context has new data - storedDatasets := GetDatasets(modifiedRequest.Context()) - if !reflect.DeepEqual(datasets, storedDatasets) { - t.Errorf("TestStoreDatasets failed, got %s, expected %s", storedDatasets, datasets) - } - -} diff --git a/api/sda/sda.go b/api/sda/sda.go index 6e2568f..7deee5c 100644 --- a/api/sda/sda.go +++ b/api/sda/sda.go @@ -2,8 +2,6 @@ package sda import ( "bytes" - "context" - "encoding/json" "errors" "fmt" "io" @@ -12,7 +10,7 @@ import ( "strconv" "strings" - "github.com/gorilla/mux" + "github.com/gin-gonic/gin" "github.com/neicnordic/crypt4gh/model/headers" "github.com/neicnordic/crypt4gh/streaming" "github.com/neicnordic/sda-download/api/middleware" @@ -31,17 +29,15 @@ func sanitizeString(str string) string { } // Datasets serves a list of permitted datasets -func Datasets(w http.ResponseWriter, r *http.Request) { +func Datasets(c *gin.Context) { log.Debugf("request permitted datasets") // Retrieve dataset list from request context // generated by the authentication middleware - datasets := middleware.GetDatasets(r.Context()) + datasets := middleware.GetDatasets(c) // Return response - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(datasets) + c.JSON(http.StatusOK, datasets) } // find looks for a dataset name in a list of datasets @@ -59,7 +55,7 @@ func find(datasetID string, datasets []string) bool { } // getFiles returns files belonging to a dataset -var getFiles = func(datasetID string, ctx context.Context) ([]*database.FileInfo, int, error) { +var getFiles = func(datasetID string, ctx *gin.Context) ([]*database.FileInfo, int, error) { // Retrieve dataset list from request context // generated by the authentication middleware @@ -84,10 +80,9 @@ var getFiles = func(datasetID string, ctx context.Context) ([]*database.FileInfo } // Files serves a list of files belonging to a dataset -func Files(w http.ResponseWriter, r *http.Request) { +func Files(c *gin.Context) { - vars := mux.Vars(r) - dataset := vars["dataset"] + dataset := c.Param("dataset") // Get optional dataset scheme // A scheme can be delivered separately in a query parameter @@ -95,7 +90,7 @@ func Files(w http.ResponseWriter, r *http.Request) { // in the path. A client can conveniently split the scheme with "://" // which results in 1 item if there is no scheme (e.g. EGAD) or 2 items // if there was a scheme (e.g. DOI) - scheme := r.URL.Query().Get("scheme") + scheme := c.Query("scheme") schemeLogs := strings.ReplaceAll(scheme, "\n", "") schemeLogs = strings.ReplaceAll(schemeLogs, "\r", "") @@ -108,36 +103,33 @@ func Files(w http.ResponseWriter, r *http.Request) { } // Get dataset files - files, code, err := getFiles(dataset, r.Context()) + files, code, err := getFiles(dataset, c) if err != nil { - http.Error(w, err.Error(), code) + c.String(code, err.Error()) return } // Return response - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(files) + c.JSON(http.StatusOK, files) } // Download serves file contents as bytes -func Download(w http.ResponseWriter, r *http.Request) { +func Download(c *gin.Context) { // Get file ID from path - vars := mux.Vars(r) - fileID := vars["fileid"] + fileID := c.Param("fileid") // Check user has permissions for this file (as part of a dataset) dataset, err := database.CheckFilePermission(fileID) if err != nil { - http.Error(w, "file not found", 404) + c.String(http.StatusNotFound, "file not found") return } // Get datasets from request context, parsed previously by token middleware - datasets := middleware.GetDatasets(r.Context()) + datasets := middleware.GetDatasets(c) // Verify user has permission to datafile permission := false @@ -150,7 +142,7 @@ func Download(w http.ResponseWriter, r *http.Request) { } if !permission { log.Debugf("user requested to view file, but does not have permissions for dataset %s", dataset) - http.Error(w, "unauthorised", 401) + c.String(http.StatusUnauthorized, "unauthorised") return } @@ -158,7 +150,7 @@ func Download(w http.ResponseWriter, r *http.Request) { // Get file header fileDetails, err := database.GetFile(fileID) if err != nil { - http.Error(w, "database error", 500) + c.String(http.StatusInternalServerError, "database error") return } @@ -167,16 +159,16 @@ func Download(w http.ResponseWriter, r *http.Request) { file, err := Backend.NewFileReader(fileDetails.ArchivePath) if err != nil { log.Errorf("could not find archive file %s, %s", fileDetails.ArchivePath, err) - http.Error(w, "archive error", 500) + c.String(http.StatusInternalServerError, "archive error") return } // Get coordinates - coordinates, err := parseCoordinates(r) + coordinates, err := parseCoordinates(c.Request) if err != nil { log.Errorf("parsing of query param coordinates to crypt4gh format failed, reason: %v", err) - http.Error(w, err.Error(), 400) + c.String(http.StatusBadRequest, err.Error()) return } @@ -185,12 +177,12 @@ func Download(w http.ResponseWriter, r *http.Request) { fileStream, err := stitchFile(fileDetails.Header, file, coordinates) if err != nil { log.Errorf("could not prepare file for streaming, %s", err) - http.Error(w, "file stream error", 500) + c.String(http.StatusInternalServerError, "file stream error") return } - sendStream(w, fileStream) + sendStream(c.Writer, fileStream) } // stitchFile stitches the header and file body together for Crypt4GHReader diff --git a/api/sda/sda_test.go b/api/sda/sda_test.go index 0cce81a..3779981 100644 --- a/api/sda/sda_test.go +++ b/api/sda/sda_test.go @@ -2,7 +2,6 @@ package sda import ( "bytes" - "context" "errors" "io" "net/http" @@ -10,6 +9,7 @@ import ( "os" "testing" + "github.com/gin-gonic/gin" "github.com/neicnordic/crypt4gh/model/headers" "github.com/neicnordic/crypt4gh/streaming" "github.com/neicnordic/sda-download/api/middleware" @@ -24,21 +24,22 @@ func TestDatasets(t *testing.T) { originalGetDatasets := middleware.GetDatasets // Substitute mock functions - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(c *gin.Context) []string { return []string{"dataset1", "dataset2"} } // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) + c.Set("datasets", []string{"dataset1", "dataset2"}) // Test the outcomes of the handler - Datasets(w, r) + Datasets(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 200 - expectedBody := []byte(`["dataset1","dataset2"]` + "\n") + expectedBody := []byte(`["dataset1","dataset2"]`) if response.StatusCode != expectedStatusCode { t.Errorf("TestDatasets failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -96,7 +97,7 @@ func TestGetFiles_Fail_Database(t *testing.T) { originalGetFilesDB := database.GetFiles // Substitute mock functions - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{"dataset1", "dataset2"} } database.GetFiles = func(datasetID string) ([]*database.FileInfo, error) { @@ -104,7 +105,9 @@ func TestGetFiles_Fail_Database(t *testing.T) { } // Run test target - fileInfo, statusCode, err := getFiles("dataset1", context.TODO()) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + fileInfo, statusCode, err := getFiles("dataset1", c) // Expected results expectedStatusCode := 500 @@ -132,12 +135,14 @@ func TestGetFiles_Fail_NotFound(t *testing.T) { originalGetDatasets := middleware.GetDatasets // Substitute mock functions - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{"dataset1", "dataset2"} } // Run test target - fileInfo, statusCode, err := getFiles("dataset3", context.TODO()) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + fileInfo, statusCode, err := getFiles("dataset3", c) // Expected results expectedStatusCode := 404 @@ -164,7 +169,7 @@ func TestGetFiles_Success(t *testing.T) { originalGetFilesDB := database.GetFiles // Substitute mock functions - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{"dataset1", "dataset2"} } database.GetFiles = func(datasetID string) ([]*database.FileInfo, error) { @@ -178,7 +183,9 @@ func TestGetFiles_Success(t *testing.T) { } // Run test target - fileInfo, statusCode, err := getFiles("dataset1", context.TODO()) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + fileInfo, statusCode, err := getFiles("dataset1", c) // Expected results expectedStatusCode := 200 @@ -206,21 +213,21 @@ func TestFiles_Fail(t *testing.T) { originalGetFiles := getFiles // Substitute mock functions - getFiles = func(datasetID string, ctx context.Context) ([]*database.FileInfo, int, error) { + getFiles = func(datasetID string, ctx *gin.Context) ([]*database.FileInfo, int, error) { return nil, 404, errors.New("dataset not found") } // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Files(w, r) + Files(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 404 - expectedBody := []byte("dataset not found\n") + expectedBody := []byte("dataset not found") if response.StatusCode != expectedStatusCode { t.Errorf("TestDatasets failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -243,7 +250,7 @@ func TestFiles_Success(t *testing.T) { originalGetFiles := getFiles // Substitute mock functions - getFiles = func(datasetID string, ctx context.Context) ([]*database.FileInfo, int, error) { + getFiles = func(datasetID string, ctx *gin.Context) ([]*database.FileInfo, int, error) { fileInfo := database.FileInfo{ FileID: "file1", DatasetID: "dataset1", @@ -263,10 +270,10 @@ func TestFiles_Success(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Files(w, r) + Files(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) @@ -274,7 +281,7 @@ func TestFiles_Success(t *testing.T) { expectedBody := []byte( `[{"fileId":"file1","datasetId":"dataset1","displayFileName":"file1.txt","fileName":` + `"file1.txt","fileSize":200,"decryptedFileSize":100,"decryptedFileChecksum":"hash",` + - `"decryptedFileChecksumType":"sha256","fileStatus":"READY"}]` + "\n") + `"decryptedFileChecksumType":"sha256","fileStatus":"READY"}]`) if response.StatusCode != expectedStatusCode { t.Errorf("TestDatasets failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -400,15 +407,15 @@ func TestDownload_Fail_FileNotFound(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Download(w, r) + Download(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 404 - expectedBody := []byte("file not found\n") + expectedBody := []byte("file not found") if response.StatusCode != expectedStatusCode { t.Errorf("TestDownload_Fail_FileNotFound failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -436,21 +443,21 @@ func TestDownload_Fail_NoPermissions(t *testing.T) { // nolint:goconst return "dataset1", nil } - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{} } // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Download(w, r) + Download(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 401 - expectedBody := []byte("unauthorised\n") + expectedBody := []byte("unauthorised") if response.StatusCode != expectedStatusCode { t.Errorf("TestDownload_Fail_NoPermissions failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -479,7 +486,7 @@ func TestDownload_Fail_GetFile(t *testing.T) { database.CheckFilePermission = func(fileID string) (string, error) { return "dataset1", nil } - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{"dataset1"} } database.GetFile = func(fileID string) (*database.FileDownload, error) { @@ -488,15 +495,15 @@ func TestDownload_Fail_GetFile(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Download(w, r) + Download(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 500 - expectedBody := []byte("database error\n") + expectedBody := []byte("database error") if response.StatusCode != expectedStatusCode { t.Errorf("TestDownload_Fail_GetFile failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -527,7 +534,7 @@ func TestDownload_Fail_OpenFile(t *testing.T) { database.CheckFilePermission = func(fileID string) (string, error) { return "dataset1", nil } - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{"dataset1"} } database.GetFile = func(fileID string) (*database.FileDownload, error) { @@ -542,15 +549,15 @@ func TestDownload_Fail_OpenFile(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Download(w, r) + Download(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 500 - expectedBody := []byte("archive error\n") + expectedBody := []byte("archive error") if response.StatusCode != expectedStatusCode { t.Errorf("TestDownload_Fail_OpenFile failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -559,7 +566,7 @@ func TestDownload_Fail_OpenFile(t *testing.T) { // visual byte comparison in terminal (easier to find string differences) t.Error(body) t.Error([]byte(expectedBody)) - t.Errorf("TestDownload_Fail_OpenFile failed, got %s expected %s", string(body), string(expectedBody)) + t.Errorf("TestDownload_Fail_OpenFile failed, got '%s' expected '%s'", string(body), string(expectedBody)) } // Return mock functions to originals @@ -583,7 +590,7 @@ func TestDownload_Fail_ParseCoordinates(t *testing.T) { database.CheckFilePermission = func(fileID string) (string, error) { return "dataset1", nil } - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{"dataset1"} } database.GetFile = func(fileID string) (*database.FileDownload, error) { @@ -601,15 +608,15 @@ func TestDownload_Fail_ParseCoordinates(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Download(w, r) + Download(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 400 - expectedBody := []byte("bad params\n") + expectedBody := []byte("bad params") if response.StatusCode != expectedStatusCode { t.Errorf("TestDownload_Fail_ParseCoordinates failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -644,7 +651,7 @@ func TestDownload_Fail_StreamFile(t *testing.T) { database.CheckFilePermission = func(fileID string) (string, error) { return "dataset1", nil } - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{"dataset1"} } database.GetFile = func(fileID string) (*database.FileDownload, error) { @@ -665,15 +672,15 @@ func TestDownload_Fail_StreamFile(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Download(w, r) + Download(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 500 - expectedBody := []byte("file stream error\n") + expectedBody := []byte("file stream error") if response.StatusCode != expectedStatusCode { t.Errorf("TestDownload_Fail_StreamFile failed, got %d expected %d", response.StatusCode, expectedStatusCode) @@ -710,7 +717,7 @@ func TestDownload_Success(t *testing.T) { database.CheckFilePermission = func(fileID string) (string, error) { return "dataset1", nil } - middleware.GetDatasets = func(ctx context.Context) []string { + middleware.GetDatasets = func(ctx *gin.Context) []string { return []string{"dataset1"} } database.GetFile = func(fileID string) (*database.FileDownload, error) { @@ -735,10 +742,10 @@ func TestDownload_Success(t *testing.T) { // Mock request and response holders w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "https://testing.fi", nil) + c, _ := gin.CreateTestContext(w) // Test the outcomes of the handler - Download(w, r) + Download(c) response := w.Result() defer response.Body.Close() body, _ := io.ReadAll(response.Body) diff --git a/go.mod b/go.mod index 342bc13..24759d0 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/aws/aws-sdk-go v1.44.230 github.com/dgraph-io/ristretto v0.1.1 + github.com/gin-gonic/gin v1.9.0 github.com/google/uuid v1.3.0 - github.com/gorilla/mux v1.8.0 github.com/johannesboyne/gofakes3 v0.0.0-20230129080941-f6a8a9ae6fd3 github.com/lestrrat-go/jwx v1.2.25 github.com/lib/pq v1.10.7 @@ -19,27 +19,38 @@ require ( require ( filippo.io/edwards25519 v1.0.0 // indirect + github.com/bytedance/sonic v1.8.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.11.2 // indirect github.com/goccy/go-json v0.10.0 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/leodido/go-urn v1.2.1 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.0 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/iter v1.0.1 // indirect github.com/lestrrat-go/option v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 // indirect github.com/spf13/afero v1.9.3 // indirect @@ -47,11 +58,15 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.9 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect golang.org/x/tools v0.6.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index a47b40a..23ed24f 100644 --- a/go.sum +++ b/go.sum @@ -45,10 +45,16 @@ github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q github.com/aws/aws-sdk-go v1.33.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.44.230 h1:dcn7TjLyx/31I+0XytMGYRxDc756BRUzsSYVcSyKZlk= github.com/aws/aws-sdk-go v1.44.230/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -79,9 +85,20 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= @@ -112,6 +129,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -123,7 +141,9 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -144,8 +164,6 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -159,16 +177,21 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/johannesboyne/gofakes3 v0.0.0-20230129080941-f6a8a9ae6fd3 h1:aTscQmvmU/1AS3PqVaNtUtJUwyMexxqVErkhwsWoEpw= github.com/johannesboyne/gofakes3 v0.0.0-20230129080941-f6a8a9ae6fd3/go.mod h1:Cnosl0cRZIfKjTMuH49sQog2LeNsU5Hf4WnPIDWIDV0= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= @@ -185,13 +208,19 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/neicnordic/crypt4gh v1.7.3 h1:GUuutiuZjbAR+Ci4O5RTJBUMJXxncf+UHoE1W6FR5uk= github.com/neicnordic/crypt4gh v1.7.3/go.mod h1:F6uTrg2YajFj2nyz1Kbdy2GQFqMstCmE85Q/iQCBH7E= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= @@ -200,7 +229,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 h1:J6qvD6rbmOil46orKqJaRPG+zTpoGlBTUdyv8ki63L0= @@ -222,6 +250,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -233,6 +262,10 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -245,6 +278,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -325,6 +360,7 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -383,6 +419,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -549,10 +586,12 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -572,5 +611,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 37b34a41add297883489544618bdb292f9985837 Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Mon, 20 Mar 2023 15:12:39 +0100 Subject: [PATCH 02/10] fix: explicitly return 405 on POST --- api/api.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/api.go b/api/api.go index 2d3b442..b87ba90 100644 --- a/api/api.go +++ b/api/api.go @@ -27,10 +27,14 @@ func Setup() *http.Server { router := gin.Default() router.GET("/metadata/datasets", middleware.TokenMiddleware(), sda.Datasets) + router.POST("/metadata/datasets", func(c *gin.Context) { c.String(http.StatusMethodNotAllowed, "Method Not Allowed") }) router.GET("/metadata/datasets/:dataset/files", middleware.TokenMiddleware(), sda.Files) router.GET("/files/:fileid", middleware.TokenMiddleware(), sda.Download) router.GET("/health", healthResponse) + // explicitly return 405 on POST requests + router.POST("/metadata/datasets", func(c *gin.Context) { c.String(http.StatusMethodNotAllowed, "Method Not Allowed") }) + // Configure TLS settings log.Info("(3/5) Configuring TLS") cfg := &tls.Config{ From 8f50d27c388af5bbda5e7f89667628960f7ffe65 Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Mon, 20 Mar 2023 15:47:19 +0100 Subject: [PATCH 03/10] fix: make dataset ids parse correctly --- api/api.go | 3 +-- api/sda/sda.go | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/api.go b/api/api.go index b87ba90..940fea7 100644 --- a/api/api.go +++ b/api/api.go @@ -27,8 +27,7 @@ func Setup() *http.Server { router := gin.Default() router.GET("/metadata/datasets", middleware.TokenMiddleware(), sda.Datasets) - router.POST("/metadata/datasets", func(c *gin.Context) { c.String(http.StatusMethodNotAllowed, "Method Not Allowed") }) - router.GET("/metadata/datasets/:dataset/files", middleware.TokenMiddleware(), sda.Files) + router.GET("/metadata/datasets/*dataset", middleware.TokenMiddleware(), sda.Files) router.GET("/files/:fileid", middleware.TokenMiddleware(), sda.Download) router.GET("/health", healthResponse) diff --git a/api/sda/sda.go b/api/sda/sda.go index 7deee5c..f692eec 100644 --- a/api/sda/sda.go +++ b/api/sda/sda.go @@ -82,7 +82,10 @@ var getFiles = func(datasetID string, ctx *gin.Context) ([]*database.FileInfo, i // Files serves a list of files belonging to a dataset func Files(c *gin.Context) { + // get dataset parameter, remove / prefix and /files suffix dataset := c.Param("dataset") + dataset = strings.TrimPrefix(dataset, "/") + dataset = strings.TrimSuffix(dataset, "/files") // Get optional dataset scheme // A scheme can be delivered separately in a query parameter From a79421935c48863c32b5e9789befd57615d51438 Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Tue, 21 Mar 2023 09:45:20 +0100 Subject: [PATCH 04/10] restore mistakenly removed tests --- api/middleware/middleware_test.go | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/api/middleware/middleware_test.go b/api/middleware/middleware_test.go index fa00746..97f09ec 100644 --- a/api/middleware/middleware_test.go +++ b/api/middleware/middleware_test.go @@ -274,3 +274,38 @@ func TestTokenMiddleware_Success_FromCache(t *testing.T) { session.Get = originalGetCache } + +func TestStoreDatasets(t *testing.T) { + // Get a request context for testing if data is saved + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + // Store data to request context + datasets := []string{"dataset1", "dataset2"} + modifiedContext := storeDatasets(c, datasets) + + // Verify that context has new data + storedDatasets := modifiedContext.Value(datasetsKey).([]string) + if !reflect.DeepEqual(datasets, storedDatasets) { + t.Errorf("TestStoreDatasets failed, got %s, expected %s", storedDatasets, datasets) + } + +} + +func TestGetDatasets(t *testing.T) { + + // Get a request context for testing if data is saved + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + // Store data to request context + datasets := []string{"dataset1", "dataset2"} + modifiedContext := storeDatasets(c, datasets) + + // Verify that context has new data + storedDatasets := GetDatasets(modifiedContext) + if !reflect.DeepEqual(datasets, storedDatasets) { + t.Errorf("TestStoreDatasets failed, got %s, expected %s", storedDatasets, datasets) + } + +} From f644526248c2e3a71796542f954121e288e52bdd Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Thu, 23 Mar 2023 08:21:29 +0100 Subject: [PATCH 05/10] fix: proper 405 handling --- api/api.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/api.go b/api/api.go index 940fea7..77ec809 100644 --- a/api/api.go +++ b/api/api.go @@ -26,14 +26,13 @@ func Setup() *http.Server { router := gin.Default() + router.HandleMethodNotAllowed = true + router.GET("/metadata/datasets", middleware.TokenMiddleware(), sda.Datasets) router.GET("/metadata/datasets/*dataset", middleware.TokenMiddleware(), sda.Files) router.GET("/files/:fileid", middleware.TokenMiddleware(), sda.Download) router.GET("/health", healthResponse) - // explicitly return 405 on POST requests - router.POST("/metadata/datasets", func(c *gin.Context) { c.String(http.StatusMethodNotAllowed, "Method Not Allowed") }) - // Configure TLS settings log.Info("(3/5) Configuring TLS") cfg := &tls.Config{ From 976a1a9a21aabfe823dc8815aa9abad9b23c332f Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Thu, 23 Mar 2023 08:23:13 +0100 Subject: [PATCH 06/10] fix: store context and remove unused comment --- api/middleware/middleware.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/middleware/middleware.go b/api/middleware/middleware.go index 304754d..b820d9a 100644 --- a/api/middleware/middleware.go +++ b/api/middleware/middleware.go @@ -10,7 +10,6 @@ import ( log "github.com/sirupsen/logrus" ) -// as specified in docs: https://pkg.go.dev/context#WithValue var datasetsKey = "datasets" // TokenMiddleware performs access token verification and validation @@ -73,7 +72,7 @@ func TokenMiddleware() gin.HandlerFunc { } // Store dataset list to request context, for use in the endpoint handlers - storeDatasets(c, datasets) + c = storeDatasets(c, datasets) // Forward request to the next endpoint handler c.Next() From 753d90bbda379419fde67ec6ba60783869114941 Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Thu, 23 Mar 2023 08:23:27 +0100 Subject: [PATCH 07/10] fix: abort on middleware error --- api/middleware/middleware.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/middleware/middleware.go b/api/middleware/middleware.go index b820d9a..9f6beeb 100644 --- a/api/middleware/middleware.go +++ b/api/middleware/middleware.go @@ -37,6 +37,7 @@ func TokenMiddleware() gin.HandlerFunc { token, code, err := auth.GetToken(c.Request.Header.Get("Authorization")) if err != nil { c.String(code, err.Error()) + c.AbortWithError(code, err) return } @@ -46,6 +47,7 @@ func TokenMiddleware() gin.HandlerFunc { if err != nil { log.Debug("failed to validate token at AAI") c.String(http.StatusUnauthorized, "bad token") + c.AbortWithError(code, err) return } From 190057e9d65caf32a1680d68a59915a908d77d74 Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Thu, 23 Mar 2023 08:42:42 +0100 Subject: [PATCH 08/10] fix: handle all errors + linting --- api/middleware/middleware.go | 12 +++++++++--- api/middleware/middleware_test.go | 2 +- cmd/main.go | 6 +++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/api/middleware/middleware.go b/api/middleware/middleware.go index 9f6beeb..476fb01 100644 --- a/api/middleware/middleware.go +++ b/api/middleware/middleware.go @@ -30,14 +30,17 @@ func TokenMiddleware() gin.HandlerFunc { datasets, exists = session.Get(sessionCookie) } - if !exists { + if !exists { //nolint:nestif log.Debug("no session found, create new session") // Check that a token is provided token, code, err := auth.GetToken(c.Request.Header.Get("Authorization")) if err != nil { c.String(code, err.Error()) - c.AbortWithError(code, err) + err := c.AbortWithError(code, err) + if err != nil { + log.Errorf("Error: %v", err) + } return } @@ -47,7 +50,10 @@ func TokenMiddleware() gin.HandlerFunc { if err != nil { log.Debug("failed to validate token at AAI") c.String(http.StatusUnauthorized, "bad token") - c.AbortWithError(code, err) + err := c.AbortWithError(code, err) + if err != nil { + log.Errorf("Error: %v", err) + } return } diff --git a/api/middleware/middleware_test.go b/api/middleware/middleware_test.go index 97f09ec..a1c2c27 100644 --- a/api/middleware/middleware_test.go +++ b/api/middleware/middleware_test.go @@ -20,7 +20,7 @@ const token string = "token" // testEndpoint mimics the endpoint handlers that perform business logic after passing the // authentication middleware. This handler is generic and can be used for all cases. -func testEndpoint(c *gin.Context) {} +func testEndpoint(_ *gin.Context) {} func TestTokenMiddleware_Fail_GetToken(t *testing.T) { diff --git a/cmd/main.go b/cmd/main.go index a27281f..bc122d6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -70,8 +70,8 @@ func main() { if config.Config.App.ServerCert != "" && config.Config.App.ServerKey != "" { log.Infof("Web server is ready to receive connections at https://%s:%d", config.Config.App.Host, config.Config.App.Port) log.Fatal(srv.ListenAndServeTLS(config.Config.App.ServerCert, config.Config.App.ServerKey)) - } else { - log.Infof("Web server is ready to receive connections at http://%s:%d", config.Config.App.Host, config.Config.App.Port) - log.Fatal(srv.ListenAndServe()) } + + log.Infof("Web server is ready to receive connections at http://%s:%d", config.Config.App.Host, config.Config.App.Port) + log.Fatal(srv.ListenAndServe()) } From 39f63688e92eeb7668befd6b5e95217028c3e9bf Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Fri, 24 Mar 2023 14:18:22 +0100 Subject: [PATCH 09/10] simplify error returns --- api/middleware/middleware.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/api/middleware/middleware.go b/api/middleware/middleware.go index 476fb01..40e1dd5 100644 --- a/api/middleware/middleware.go +++ b/api/middleware/middleware.go @@ -30,17 +30,14 @@ func TokenMiddleware() gin.HandlerFunc { datasets, exists = session.Get(sessionCookie) } - if !exists { //nolint:nestif + if !exists { log.Debug("no session found, create new session") // Check that a token is provided token, code, err := auth.GetToken(c.Request.Header.Get("Authorization")) if err != nil { c.String(code, err.Error()) - err := c.AbortWithError(code, err) - if err != nil { - log.Errorf("Error: %v", err) - } + c.AbortWithStatus(code) return } @@ -50,10 +47,7 @@ func TokenMiddleware() gin.HandlerFunc { if err != nil { log.Debug("failed to validate token at AAI") c.String(http.StatusUnauthorized, "bad token") - err := c.AbortWithError(code, err) - if err != nil { - log.Errorf("Error: %v", err) - } + c.AbortWithStatus(code) return } From ab892a7055b8e402c082108e1c85fe369e42d1dd Mon Sep 17 00:00:00 2001 From: Martin Norling Date: Wed, 29 Mar 2023 10:41:10 +0200 Subject: [PATCH 10/10] change "bad token" error to "get visas failed" --- .github/integration/tests/common/50_check_endpoint.sh | 4 ++-- .github/integration/tests/s3notls/52_check_endpoint.sh | 6 +++--- api/middleware/middleware.go | 2 +- api/middleware/middleware_test.go | 4 ++-- dev_utils/run_integration_test.sh | 4 ++-- dev_utils/run_integration_test_no_tls.sh | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/integration/tests/common/50_check_endpoint.sh b/.github/integration/tests/common/50_check_endpoint.sh index 0aa4b3e..05886a3 100755 --- a/.github/integration/tests/common/50_check_endpoint.sh +++ b/.github/integration/tests/common/50_check_endpoint.sh @@ -59,7 +59,7 @@ fi echo "expected dataset found" -## Test datasets/files endpoint +## Test datasets/files endpoint check_files=$(curl --cacert certs/ca.pem -H "Authorization: Bearer $token" "https://localhost:8443/metadata/datasets/https://doi.example/ty009.sfrrss/600.45asasga/files" | jq -r '.[0].fileId') @@ -91,7 +91,7 @@ else fi # ------------------ -# Test bad token +# Test get visas failed token=$(curl --cacert certs/ca.pem "https://localhost:8000/tokens" | jq -r '.[1]') diff --git a/.github/integration/tests/s3notls/52_check_endpoint.sh b/.github/integration/tests/s3notls/52_check_endpoint.sh index b65d60b..4b26f38 100644 --- a/.github/integration/tests/s3notls/52_check_endpoint.sh +++ b/.github/integration/tests/s3notls/52_check_endpoint.sh @@ -55,7 +55,7 @@ fi echo "expected dataset found" -## Test datasets/files endpoint +## Test datasets/files endpoint check_files=$(curl -H "Authorization: Bearer $token" "http://localhost:8080/metadata/datasets/https://doi.example/ty009.sfrrss/600.45asasga/files" | jq -r '.[0].fileId') @@ -78,7 +78,7 @@ crypt4gh decrypt --sk c4gh.sec.pem < dummy_data.c4gh > old-file.txt curl -H "Authorization: Bearer $token" "http://localhost:8080/files/urn:neic:001-002" --output test-download.txt -cmp --silent old-file.txt test-download.txt +cmp --silent old-file.txt test-download.txt status=$? if [[ $status = 0 ]]; then echo "Files are the same" @@ -87,7 +87,7 @@ else fi # ------------------ -# Test bad token +# Test get visas failed token=$(curl --cacert certs/ca.pem "https://localhost:8000/tokens" | jq -r '.[1]') diff --git a/api/middleware/middleware.go b/api/middleware/middleware.go index 40e1dd5..dcec57b 100644 --- a/api/middleware/middleware.go +++ b/api/middleware/middleware.go @@ -46,7 +46,7 @@ func TokenMiddleware() gin.HandlerFunc { visas, err := auth.GetVisas(auth.Details, token) if err != nil { log.Debug("failed to validate token at AAI") - c.String(http.StatusUnauthorized, "bad token") + c.String(http.StatusUnauthorized, "get visas failed") c.AbortWithStatus(code) return diff --git a/api/middleware/middleware_test.go b/api/middleware/middleware_test.go index a1c2c27..4a30d09 100644 --- a/api/middleware/middleware_test.go +++ b/api/middleware/middleware_test.go @@ -74,7 +74,7 @@ func TestTokenMiddleware_Fail_GetVisas(t *testing.T) { return token, 200, nil } auth.GetVisas = func(o auth.OIDCDetails, token string) (*auth.Visas, error) { - return nil, errors.New("bad token") + return nil, errors.New("get visas failed") } // Mock request and response holders @@ -91,7 +91,7 @@ func TestTokenMiddleware_Fail_GetVisas(t *testing.T) { defer response.Body.Close() body, _ := io.ReadAll(response.Body) expectedStatusCode := 401 - expectedBody := []byte("bad token") + expectedBody := []byte("get visas failed") if response.StatusCode != expectedStatusCode { t.Errorf("TestTokenMiddleware_Fail_GetVisas failed, got %d expected %d", response.StatusCode, expectedStatusCode) diff --git a/dev_utils/run_integration_test.sh b/dev_utils/run_integration_test.sh index 2000158..be4277f 100644 --- a/dev_utils/run_integration_test.sh +++ b/dev_utils/run_integration_test.sh @@ -40,7 +40,7 @@ docker run --rm --name client --network dev_utils_default -v "$PWD/certs:/certs" -t -c "INSERT INTO local_ega_ebi.filedataset (id, file_id, dataset_stable_id) VALUES (1, 1, 'https://doi.example/ty009.sfrrss/600.45asasga');" -# Make buckets if they don't exist already +# Make buckets if they don't exist already s3cmd -c s3cmd.conf mb s3://archive || true # Upload test file @@ -140,7 +140,7 @@ else echo "Files are different" fi -# Test bad token +# Test get visas failed token=$(curl --cacert certs/ca.pem "https://localhost:8000/tokens" | jq -r '.[1]') diff --git a/dev_utils/run_integration_test_no_tls.sh b/dev_utils/run_integration_test_no_tls.sh index df557a2..c0e6397 100644 --- a/dev_utils/run_integration_test_no_tls.sh +++ b/dev_utils/run_integration_test_no_tls.sh @@ -40,7 +40,7 @@ docker run --rm --name client --network dev_utils_default -v "$PWD/certs:/certs" -t -c "INSERT INTO local_ega_ebi.filedataset (id, file_id, dataset_stable_id) VALUES (1, 1, 'https://doi.example/ty009.sfrrss/600.45asasga');" -# Make buckets if they don't exist already +# Make buckets if they don't exist already s3cmd -c s3cmd-notls.conf mb s3://archive || true # Upload test file @@ -98,7 +98,7 @@ fi echo "expected dataset found" -## Test datasets/files endpoint +## Test datasets/files endpoint check_files=$(curl -H "Authorization: Bearer $token" "http://localhost:8080/metadata/datasets/https://doi.example/ty009.sfrrss/600.45asasga/files" | jq -r '.[0].fileId') @@ -120,7 +120,7 @@ crypt4gh decrypt --sk c4gh.sec.pem < dummy_data.c4gh > old-file.txt curl -H "Authorization: Bearer $token" "http://localhost:8080/files/urn:neic:001-002" --output test-download.txt -cmp --silent old-file.txt test-download.txt +cmp --silent old-file.txt test-download.txt status=$? if [ $status = 0 ]; then echo "Files are the same" @@ -128,7 +128,7 @@ else echo "Files are different" fi -# Test bad token +# Test get visas failed token=$(curl --cacert certs/ca.pem "https://localhost:8000/tokens" | jq -r '.[1]')