From 570e8ab10e6a032da92f9e2d4fec6a17ba0b89c7 Mon Sep 17 00:00:00 2001 From: Kavya Shukla Date: Mon, 26 Jun 2023 22:07:29 +0530 Subject: [PATCH 1/2] feat: added authentication in API and search api Get license with specific search term: field: represents the name of field to be searched search_term: represents the term to be searched search: represent the algorithm to search with authenticate the API: added the struct for user created a user table in the database added basic user endpoints: get user using id get all user create user added basic authentication to all the API endpoints i.e, group of endpoints Signed-off-by: Kavya Shukla --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ec4c81d..a5f8400 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ *.dll *.so *.dylib -laas +/laas # Test binary, built with `go test -c` *.test From 1df70a575d10f1fe89259e78dbb7ad42125388fc Mon Sep 17 00:00:00 2001 From: Kavya Shukla Date: Wed, 9 Aug 2023 00:14:55 +0530 Subject: [PATCH 2/2] feat: added audit and change history endpoints. - /api/audit: to get all the audit logs - /api/audit/:audit_id: to get the audit by its id - /api/audit/:audit_id/changes: to get all the change log of a particular audit - /api/audit/:audit_id/changes/:id: to get change of a particular change log Signed-off-by: Kavya Shukla --- cmd/laas/main.go | 8 ++ pkg/api/api.go | 315 +++++++++++++++++++++++++++++++++++++++++++- pkg/api/api_test.go | 8 +- pkg/auth/auth.go | 4 +- pkg/models/types.go | 32 ++++- 5 files changed, 357 insertions(+), 10 deletions(-) diff --git a/cmd/laas/main.go b/cmd/laas/main.go index c17ff99..235c97d 100644 --- a/cmd/laas/main.go +++ b/cmd/laas/main.go @@ -43,8 +43,16 @@ func main() { log.Fatalf("Failed to automigrate database: %v", err) } + if err := db.DB.AutoMigrate(&models.Audit{}); err != nil { + log.Fatalf("Failed to automigrate database: %v", err) + } + + if err := db.DB.AutoMigrate(&models.ChangeLog{}); err != nil { + log.Fatalf("Failed to automigrate database: %v", err) + } db.Populatedb(*populatedb, *datafile) r := api.Router() + r.Run() } diff --git a/pkg/api/api.go b/pkg/api/api.go index 8bd71b9..5f7d01b 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -6,6 +6,7 @@ package api import ( "fmt" "net/http" + "strconv" "time" "github.com/fossology/LicenseDb/pkg/auth" @@ -36,6 +37,11 @@ func Router() *gin.Engine { authorized.GET("/api/users", auth.GetAllUser) authorized.GET("/api/users/:id", auth.GetUser) + authorized.GET("/api/audit", GetAllAudit) + authorized.GET("/api/audit/:audit_id", GetAudit) + authorized.GET("/api/audit/:audit_id/changes", GetChangeLog) + authorized.GET("/api/audit/:audit_id/changes/:id", GetChangeLogbyId) + return r } @@ -168,6 +174,10 @@ func CreateLicense(c *gin.Context) { func UpdateLicense(c *gin.Context) { var update models.LicenseDB var license models.LicenseDB + var oldlicense models.LicenseDB + + username := c.GetString("username") + shortname := c.Param("shortname") if err := db.DB.Where("shortname = ?", shortname).First(&license).Error; err != nil { er := models.LicenseError{ @@ -180,6 +190,7 @@ func UpdateLicense(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } + oldlicense = license if err := c.ShouldBindJSON(&update); err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, @@ -202,6 +213,7 @@ func UpdateLicense(c *gin.Context) { c.JSON(http.StatusInternalServerError, er) return } + res := models.LicenseResponse{ Data: []models.LicenseDB{license}, Status: http.StatusOK, @@ -209,7 +221,194 @@ func UpdateLicense(c *gin.Context) { ResourceCount: 1, }, } + audit := models.Audit{ + Username: username, + Shortname: shortname, + Timestamp: time.Now().Format(time.RFC3339), + } + db.DB.Create(&audit) + + if oldlicense.Shortname != license.Shortname { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "shortname", + OldValue: oldlicense.Shortname, + UpdatedValue: license.Shortname, + } + db.DB.Create(&change) + } + if oldlicense.Fullname != license.Fullname { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "fullname", + OldValue: oldlicense.Fullname, + UpdatedValue: license.Fullname, + } + db.DB.Create(&change) + } + if oldlicense.Url != license.Url { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Url", + OldValue: oldlicense.Url, + UpdatedValue: license.Url, + } + db.DB.Create(&change) + } + if oldlicense.AddDate != license.AddDate { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Adddate", + OldValue: oldlicense.AddDate, + UpdatedValue: license.AddDate, + } + db.DB.Create(&change) + } + if oldlicense.Active != license.Active { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Active", + OldValue: oldlicense.Active, + UpdatedValue: license.Active, + } + db.DB.Create(&change) + } + if oldlicense.Copyleft != license.Copyleft { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Copyleft", + OldValue: oldlicense.Copyleft, + UpdatedValue: license.Copyleft, + } + db.DB.Create(&change) + } + if oldlicense.FSFfree != license.FSFfree { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "FSFfree", + OldValue: oldlicense.FSFfree, + UpdatedValue: license.FSFfree, + } + db.DB.Create(&change) + } + if oldlicense.GPLv2compatible != license.GPLv2compatible { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "GPLv2compatible", + OldValue: oldlicense.GPLv2compatible, + UpdatedValue: license.GPLv2compatible, + } + db.DB.Create(&change) + } + if oldlicense.GPLv3compatible != license.GPLv3compatible { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "GPLv3compatible", + OldValue: oldlicense.GPLv3compatible, + UpdatedValue: license.GPLv3compatible, + } + db.DB.Create(&change) + } + if oldlicense.OSIapproved != license.OSIapproved { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "OSIapproved", + OldValue: oldlicense.Shortname, + UpdatedValue: license.Shortname, + } + db.DB.Create(&change) + } + if oldlicense.Text != license.Text { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Text", + OldValue: oldlicense.Text, + UpdatedValue: license.Text, + } + db.DB.Create(&change) + } + if oldlicense.TextUpdatable != license.TextUpdatable { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "TextUpdatable", + OldValue: oldlicense.TextUpdatable, + UpdatedValue: license.TextUpdatable, + } + db.DB.Create(&change) + } + if oldlicense.Fedora != license.Fedora { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Fedora", + OldValue: oldlicense.Fedora, + UpdatedValue: license.Fedora, + } + db.DB.Create(&change) + } + if oldlicense.Flag != license.Flag { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Flag", + OldValue: oldlicense.Shortname, + UpdatedValue: license.Shortname, + } + db.DB.Create(&change) + } + if oldlicense.Notes != license.Notes { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Notes", + OldValue: oldlicense.Notes, + UpdatedValue: license.Notes, + } + db.DB.Create(&change) + } + if oldlicense.DetectorType != license.DetectorType { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "DetectorType", + OldValue: oldlicense.DetectorType, + UpdatedValue: license.DetectorType, + } + db.DB.Create(&change) + } + if oldlicense.Source != license.Source { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Source", + OldValue: oldlicense.Source, + UpdatedValue: license.Source, + } + db.DB.Create(&change) + } + if oldlicense.SpdxId != license.SpdxId { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "SpdxId", + OldValue: oldlicense.SpdxId, + UpdatedValue: license.SpdxId, + } + db.DB.Create(&change) + } + if oldlicense.Risk != license.Risk { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Risk", + OldValue: oldlicense.Risk, + UpdatedValue: license.Risk, + } + db.DB.Create(&change) + } + if oldlicense.Marydone != license.Marydone { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Marydone", + OldValue: oldlicense.Marydone, + UpdatedValue: license.Marydone, + } + db.DB.Create(&change) + } c.JSON(http.StatusOK, res) } @@ -309,9 +508,9 @@ func SearchInLicense(c *gin.Context) { var license []models.LicenseDB query := db.DB.Model(&license) - if input.SearchType == "fuzzy" { + if input.Search == "fuzzy" { query = query.Where(fmt.Sprintf("%s ILIKE ?", input.Field), fmt.Sprintf("%%%s%%", input.SearchTerm)) - } else if input.SearchType == "" || input.SearchType == "full_text_search" { + } else if input.Search == "" || input.Search == "full_text_search" { query = query.Where(input.Field+" @@ plainto_tsquery(?)", input.SearchTerm) } else { @@ -337,3 +536,115 @@ func SearchInLicense(c *gin.Context) { c.JSON(http.StatusOK, res) } + +func GetAllAudit(c *gin.Context) { + var audit []models.Audit + + if err := db.DB.Find(&audit).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Change log not found", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + res := models.AuditResponse{ + Data: audit, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: len(audit), + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetAudit(c *gin.Context) { + var chngelog models.Audit + id := c.Param("audit_id") + + if err := db.DB.Where("id = ?", id).First(&chngelog).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no change log with such id exists", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + res := models.AuditResponse{ + Data: []models.Audit{chngelog}, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetChangeLog(c *gin.Context) { + var changelog []models.ChangeLog + id := c.Param("audit_id") + + if err := db.DB.Where("audit_id = ?", id).Find(&changelog).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no change log with such id exists", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + + res := models.ChangeLogResponse{ + Data: changelog, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetChangeLogbyId(c *gin.Context) { + var changelog models.ChangeLog + auditid := c.Param("audit_id") + id := c.Param("id") + + if err := db.DB.Where("id = ?", id).Find(&changelog).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no change history with such id exists", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + audit_id, _ := strconv.Atoi(auditid) + if changelog.AuditId != audit_id { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no change history with such id and audit id exists", + Error: "Invalid change history for the requested audit id", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + res := models.ChangeLogResponse{ + Data: []models.ChangeLog{changelog}, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + c.JSON(http.StatusOK, res) +} diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index 746039d..edf232d 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -132,7 +132,7 @@ func TestSearchInLicense(t *testing.T) { search := models.SearchLicense{ Field: "fullname", SearchTerm: "Postgresql", - SearchType: "", + Search: "", } w := makeRequest("POST", "/api/search", search, false) assert.Equal(t, http.StatusOK, w.Code) @@ -176,7 +176,7 @@ func TestSearchInLicense2(t *testing.T) { search := models.SearchLicense{ Field: "url", SearchTerm: "http://ac-archive.sourceforge.net/doc/copyright.html", - SearchType: "", + Search: "", } w := makeRequest("POST", "/api/search", search, false) assert.Equal(t, http.StatusOK, w.Code) @@ -189,7 +189,7 @@ func TestSearchInLicense2(t *testing.T) { func TestGetUser(t *testing.T) { expectUser := models.User{ - Userid: "1", + UserId: "1", Username: "fossy", Userpassword: "fossy", Userlevel: "admin", @@ -206,7 +206,7 @@ func TestGetUser(t *testing.T) { func TestCreateUser(t *testing.T) { user := models.User{ - Userid: "2", + UserId: "2", Username: "general_user", Userpassword: "abc123", Userlevel: "participant", diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 79843d4..494273d 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -33,7 +33,7 @@ func CreateUser(c *gin.Context) { er := models.LicenseError{ Status: http.StatusBadRequest, Message: "can not create user with same userid", - Error: fmt.Sprintf("Error: License with userid '%s' already exists", user.Userid), + Error: fmt.Sprintf("Error: License with userid '%s' already exists", user.UserId), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } @@ -172,7 +172,7 @@ func AuthenticationMiddleware() gin.HandlerFunc { c.Abort() return } - + c.Set("username", username) c.Next() } } diff --git a/pkg/models/types.go b/pkg/models/types.go index 7935a1a..4d5eedc 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -110,7 +110,7 @@ type LicenseInput struct { // User struct is representation of user information. type User struct { - Userid string `json:"userid" gorm:"primary_key" binding:"required"` + UserId string `json:"userid" gorm:"primary_key" binding:"required"` Username string `json:"username" gorm:"unique" binding:"required"` Userlevel string `json:"userlevel" gorm:"unique" binding:"required"` Userpassword string `json:"userpassword" gorm:"unique" binding:"required"` @@ -123,8 +123,36 @@ type UserResponse struct { Meta PaginationMeta `json:"paginationmeta"` } +type Audit struct { + Id int `json:"id" gorm:"primary_key"` + Username string `json:"username"` + Shortname string `json:"shortname"` + Timestamp string `json:"timestamp"` +} + type SearchLicense struct { Field string `json:"field" binding:"required"` SearchTerm string `json:"search_term" binding:"required"` - SearchType string `json:"search_type"` + Search string `json:"search"` +} + +type ChangeLog struct { + Id int `json:"id" gorm:"primary_key"` + Field string `json:"field"` + UpdatedValue string `json:"updated_value"` + OldValue string `json:"old_value"` + AuditId int `json:"audit_id"` + Audit Audit `gorm:"foreignKey:AuditId;references:Id" json:"-"` +} + +type ChangeLogResponse struct { + Status int `json:"status"` + Data []ChangeLog `json:"data"` + Meta PaginationMeta `json:"paginationmeta"` +} + +type AuditResponse struct { + Status int `json:"status"` + Data []Audit `json:"data"` + Meta PaginationMeta `json:"paginationmeta"` }