diff --git a/cmd/laas/main.go b/cmd/laas/main.go index 0cef70d..a979df3 100644 --- a/cmd/laas/main.go +++ b/cmd/laas/main.go @@ -11,6 +11,7 @@ import ( "log" "github.com/fossology/LicenseDb/pkg/api" + "github.com/fossology/LicenseDb/pkg/auth" "github.com/fossology/LicenseDb/pkg/models" "github.com/fossology/LicenseDb/pkg/utils" "github.com/gin-gonic/gin" @@ -50,6 +51,9 @@ func main() { log.Fatalf("Failed to automigrate database: %v", err) } + if err := database.AutoMigrate(&models.User{}); err != nil { + log.Fatalf("Failed to automigrate database: %v", err) + } if *populatedb { var licenses []models.LicenseJson // read the file of data @@ -68,9 +72,14 @@ func main() { r := gin.Default() r.NoRoute(api.HandleInvalidUrl) - r.GET("/api/licenses", api.GetAllLicense) - r.GET("/api/license/:shortname", api.GetLicense) - r.POST("/api/license", api.CreateLicense) - r.PATCH("/api/license/:shortname", api.UpdateLicense) + authorized := r.Group("/") + authorized.Use(auth.AuthenticationMiddleware()) + authorized.GET("/api/license/:shortname", api.GetLicense) + authorized.POST("/api/license", api.CreateLicense) + authorized.PATCH("/api/license/update/:shortname", api.UpdateLicense) + authorized.GET("/api/licenses", api.SearchInLicense) + r.POST("/api/user", auth.CreateUser) + authorized.GET("/api/users", auth.GetAllUser) + authorized.GET("/api/user/:id", auth.GetUser) r.Run() } diff --git a/pkg/api/api.go b/pkg/api/api.go index 853e9b3..9ee1638 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -188,3 +188,51 @@ func UpdateLicense(c *gin.Context) { c.JSON(http.StatusOK, res) } + +func SearchInLicense(c *gin.Context) { + field := c.Query("field") + search_term := c.Query("search_term") + search := c.Query("search") + if field == "" && search_term == "" { + GetAllLicense(c) + return + } + var query *gorm.DB + var license []models.LicenseDB + if search == "fuzzy" { + query = DB.Where(fmt.Sprintf("%s ILIKE ?", field), fmt.Sprintf("%%%s%%", search_term)).Find(&license) + } else if search == "" || search == "full_text_search" { + query = DB.Where(field+" @@ plainto_tsquery(?)", search_term).Find(&license) + } else { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "search algorithm doesn't exist", + Error: "search algorithm with such name doesn't exists", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + + if err := query.Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "incorrect query to search in the database", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + res := models.LicenseResponse{ + Data: license, + Status: http.StatusOK, + Meta: models.Meta{ + ResourceCount: len(license), + }, + } + c.JSON(http.StatusOK, res) + +} diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go new file mode 100644 index 0000000..7891e80 --- /dev/null +++ b/pkg/auth/auth.go @@ -0,0 +1,188 @@ +// SPDX-FileCopyrightText: 2023 Kavya Shukla +// SPDX-License-Identifier: GPL-2.0-only + +package auth + +import ( + "encoding/base64" + "fmt" + "net/http" + "strings" + "time" + + "github.com/fossology/LicenseDb/pkg/api" + "github.com/fossology/LicenseDb/pkg/models" + "github.com/gin-gonic/gin" +) + +func CreateUser(c *gin.Context) { + var user models.User + if err := c.ShouldBindJSON(&user); err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "invalid json body", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + result := api.DB.FirstOrCreate(&user) + if result.RowsAffected == 0 { + 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), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + if result.Error != nil { + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to create user", + Error: result.Error.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + res := models.UserResponse{ + Data: []models.User{user}, + Status: http.StatusCreated, + Meta: models.Meta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusCreated, res) +} + +func GetAllUser(c *gin.Context) { + var users []models.User + if err := api.DB.Find(&users).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "can not create user", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + } + res := models.UserResponse{ + Data: users, + Status: http.StatusOK, + Meta: models.Meta{ + ResourceCount: len(users), + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetUser(c *gin.Context) { + var user models.User + id := c.Param("id") + + if err := api.DB.Where("userid = ?", id).First(&user).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no user with such user id exists", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + res := models.UserResponse{ + Data: []models.User{user}, + Status: http.StatusOK, + Meta: models.Meta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusOK, res) +} + +func AuthenticationMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Please check your credentials and try again", + Error: "no credentials were passed", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + + c.JSON(http.StatusUnauthorized, er) + c.Abort() + return + } + + decodedAuth, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, "Basic ")) + if err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Please check your credentials and try again", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + + c.JSON(http.StatusBadRequest, er) + c.Abort() + return + } + + auth := strings.SplitN(string(decodedAuth), ":", 2) + if len(auth) != 2 { + c.AbortWithStatus(http.StatusBadRequest) + return + } + + username := auth[0] + password := auth[1] + + var user models.User + + err = api.DB.Where("username = ?", username).First(&user).Error + if err != nil { + er := models.LicenseError{ + Status: http.StatusUnauthorized, + Message: "User name not found", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + + c.JSON(http.StatusUnauthorized, er) + c.Abort() + return + } + + // Check if the password matches + if user.Userpassword != password { + er := models.LicenseError{ + Status: http.StatusUnauthorized, + Message: "Incorrect password", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + + c.JSON(http.StatusUnauthorized, er) + c.Abort() + return + } + + c.Next() + } +} diff --git a/pkg/authenticate/doc.go b/pkg/auth/doc.go similarity index 89% rename from pkg/authenticate/doc.go rename to pkg/auth/doc.go index bae6d1b..6bee1fa 100644 --- a/pkg/authenticate/doc.go +++ b/pkg/auth/doc.go @@ -2,4 +2,4 @@ // SPDX-License-Identifier: GPL-2.0-only // Package authenticate is build to authenticate the API. -package authenticate +package auth diff --git a/pkg/models/types.go b/pkg/models/types.go index 346ecb2..8780b60 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -107,3 +107,18 @@ type LicenseInput struct { Flag string `json:"rf_flag"` Marydone string `json:"marydone"` } + +// User struct is representation of user information. +type User struct { + 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"` +} + +// UserResponse struct is representation of design API response of user. +type UserResponse struct { + Status int `json:"status"` + Data []User `json:"data"` + Meta Meta `json:"meta"` +}