From fd1e382f70be038e18fe44a8c1cb56c78f0071aa Mon Sep 17 00:00:00 2001 From: Joakim Bygdell Date: Mon, 28 Oct 2024 15:06:37 +0100 Subject: [PATCH] [api] list key hashes --- sda/cmd/api/api.go | 22 ++++++++++++ sda/cmd/api/api_test.go | 37 ++++++++++++++++++++ sda/internal/database/db_functions.go | 40 ++++++++++++++++++++++ sda/internal/database/db_functions_test.go | 30 ++++++++++++++++ sda/internal/database/db_test.go | 2 +- 5 files changed, 130 insertions(+), 1 deletion(-) diff --git a/sda/cmd/api/api.go b/sda/cmd/api/api.go index b0efe1476..7d7b6ebf4 100644 --- a/sda/cmd/api/api.go +++ b/sda/cmd/api/api.go @@ -87,6 +87,7 @@ func setup(config *config.Config) *http.Server { r.POST("/dataset/create", isAdmin(), createDataset) // maps a set of files to a dataset r.POST("/dataset/release/*dataset", isAdmin(), releaseDataset) // Releases a dataset to be accessible r.POST("/c4gh-keys/add", isAdmin(), addC4ghHash) // Adds a key hash to the database + r.GET("/c4gh-keys/list", isAdmin(), listC4ghHashes) // Lists keyhashes in the database r.GET("/users", isAdmin(), listActiveUsers) // Lists all users r.GET("/users/:username/files", isAdmin(), listUserFiles) // Lists all unmapped files for a user } @@ -501,3 +502,24 @@ func addC4ghHash(c *gin.Context) { c.Status(http.StatusOK) } + +func listC4ghHashes(c *gin.Context) { + hashes, err := Conf.API.DB.ListKeyHashes() + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error()) + + return + } + + for n, h := range hashes { + ct, _ := time.Parse(time.RFC3339, h.CreatedAt) + hashes[n].CreatedAt = ct.Format(time.DateTime) + + if h.DeprecatedAt != "" { + dt, _ := time.Parse(time.RFC3339, h.DeprecatedAt) + hashes[n].DeprecatedAt = dt.Format(time.DateTime) + } + } + c.Writer.Header().Set("Content-Type", "application/json") + c.JSON(200, hashes) +} \ No newline at end of file diff --git a/sda/cmd/api/api_test.go b/sda/cmd/api/api_test.go index 9332893ae..7ce8e2f58 100644 --- a/sda/cmd/api/api_test.go +++ b/sda/cmd/api/api_test.go @@ -1374,3 +1374,40 @@ func (suite *TestSuite) TestAddC4ghHash_notBase64() { assert.Equal(suite.T(), http.StatusBadRequest, resp.StatusCode) defer resp.Body.Close() } + +func (suite *TestSuite) TestListC4ghHashes() { + assert.NoError(suite.T(), Conf.API.DB.AddKeyHash("cbd8f5cc8d936ce437a52cd7991453839581fc69ee26e0daefde6a5d2660fc23", "this is a test key"), "failed to register key in database") + + expectedResponse := database.C4ghKeyHash{ + Hash: "cbd8f5cc8d936ce437a52cd7991453839581fc69ee26e0daefde6a5d2660fc23", + Description: "this is a test key", + CreatedAt: time.Now().UTC().Format(time.DateTime), + DeprecatedAt: "", + } + + gin.SetMode(gin.ReleaseMode) + assert.NoError(suite.T(), setupJwtAuth()) + Conf.API.Admins = []string{"dummy"} + + r := gin.Default() + r.GET("/c4gh-keys/list", isAdmin(), listC4ghHashes) + ts := httptest.NewServer(r) + defer ts.Close() + + client := &http.Client{} + assert.NoError(suite.T(), setupJwtAuth()) + + req, err := http.NewRequest("GET", ts.URL+"/c4gh-keys/list", nil) + assert.NoError(suite.T(), err) + req.Header.Add("Authorization", "Bearer "+suite.Token) + + resp, err := client.Do(req) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), http.StatusOK, resp.StatusCode) + defer resp.Body.Close() + + hashes := []database.C4ghKeyHash{} + err = json.NewDecoder(resp.Body).Decode(&hashes) + assert.NoError(suite.T(), err, "failed to list users from DB") + assert.Equal(suite.T(), expectedResponse, hashes[0]) +} diff --git a/sda/internal/database/db_functions.go b/sda/internal/database/db_functions.go index f9cf2eb66..5b30bada8 100644 --- a/sda/internal/database/db_functions.go +++ b/sda/internal/database/db_functions.go @@ -3,6 +3,7 @@ package database import ( + "database/sql" "encoding/hex" "errors" "math" @@ -782,3 +783,42 @@ func (dbs *SDAdb) addKeyHash(keyHash, keyDescription string) error { return nil } + +type C4ghKeyHash struct { + Hash string `json:"hash"` + Description string `json:"description"` + CreatedAt string `json:"created_at"` + DeprecatedAt string `json:"deprecated_at"` +} + +// ListKeyHashes lists the hashes from the encryption_keys table +func (dbs *SDAdb) ListKeyHashes() ([]C4ghKeyHash, error) { + dbs.checkAndReconnectIfNeeded() + db := dbs.DB + + const query = "SELECT key_hash, description, created_at, deprecated_at FROM sda.encryption_keys ORDER BY created_at ASC;" + + hashList := []C4ghKeyHash{} + rows, err := db.Query(query) + if err != nil { + return nil, err + } + if rows.Err() != nil { + return nil, rows.Err() + } + defer rows.Close() + + for rows.Next() { + h := &C4ghKeyHash{} + depr := sql.NullString{} + err := rows.Scan(&h.Hash, &h.Description, &h.CreatedAt, &depr) + if err != nil { + return nil, err + } + h.DeprecatedAt = depr.String + + hashList = append(hashList, *h) + } + + return hashList, nil +} diff --git a/sda/internal/database/db_functions_test.go b/sda/internal/database/db_functions_test.go index 2b2235736..45c6a72d2 100644 --- a/sda/internal/database/db_functions_test.go +++ b/sda/internal/database/db_functions_test.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "fmt" "regexp" + "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -657,3 +658,32 @@ func (suite *DatabaseTests) TestAddKeyHash() { assert.NoError(suite.T(), err, "failed to verify key hash existence") assert.True(suite.T(), exists, "key hash was not added to the database") } + +func (suite *DatabaseTests) TestListKeyHashes() { + db, err := NewSDAdb(suite.dbConf) + assert.NoError(suite.T(), err, "got (%v) when creating new connection", err) + + assert.NoError(suite.T(), db.AddKeyHash("cbd8f5cc8d936ce437a52cd7991453839581fc69ee26e0daefde6a5d2660fc23", "this is a test key"), "failed to register key in database") + assert.NoError(suite.T(), db.AddKeyHash("cbd8f5cc8d936ce437a52cd7991453839581fc69ee26e0daefde6a5d2660fc99", "this is a another key"), "failed to register key in database") + + expectedResponse := C4ghKeyHash{ + Hash: "cbd8f5cc8d936ce437a52cd7991453839581fc69ee26e0daefde6a5d2660fc23", + Description: "this is a test key", + CreatedAt: time.Now().UTC().Format(time.DateOnly), + DeprecatedAt: "", + } + hashList, err := db.ListKeyHashes() + ct, _ := time.Parse(time.RFC3339, hashList[0].CreatedAt) + hashList[0].CreatedAt = ct.Format(time.DateOnly) + assert.NoError(suite.T(), err, "failed to verify key hash existence") + assert.Equal(suite.T(), expectedResponse, hashList[0], "key hash was not added to the database") +} + +func (suite *DatabaseTests) TestListKeyHashes_emptyTable() { + db, err := NewSDAdb(suite.dbConf) + assert.NoError(suite.T(), err, "got (%v) when creating new connection", err) + + hashList, err := db.ListKeyHashes() + assert.NoError(suite.T(), err, "failed to verify key hash existence") + assert.Equal(suite.T(), []C4ghKeyHash{}, hashList, "fuu") +} \ No newline at end of file diff --git a/sda/internal/database/db_test.go b/sda/internal/database/db_test.go index 9868fcc6f..554a2385b 100644 --- a/sda/internal/database/db_test.go +++ b/sda/internal/database/db_test.go @@ -120,7 +120,7 @@ func (suite *DatabaseTests) SetupTest() { db, err := NewSDAdb(suite.dbConf) assert.Nil(suite.T(), err, "got %v when creating new connection", err) - _, err = db.DB.Exec("TRUNCATE sda.files CASCADE") + _, err = db.DB.Exec("TRUNCATE sda.files, sda.encryption_keys CASCADE") assert.NoError(suite.T(), err) }