From cb815264631d983f85304a93735228e976ef528b Mon Sep 17 00:00:00 2001 From: Kavya Shukla Date: Mon, 26 Jun 2023 22:07:29 +0530 Subject: [PATCH] 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 --- cmd/laas/main.go | 17 +++- pkg/api/api.go | 50 +++++++++++ pkg/authenticate/auth.go | 181 +++++++++++++++++++++++++++++++++++++++ pkg/models/types.go | 15 ++++ 4 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 pkg/authenticate/auth.go diff --git a/cmd/laas/main.go b/cmd/laas/main.go index 0cef70d..67cfa5f 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/authenticate" "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(authenticate.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", authenticate.CreateUser) + authorized.GET("/api/users", authenticate.GetAllUser) + authorized.GET("/api/user/:id", authenticate.GetUser) r.Run() } diff --git a/pkg/api/api.go b/pkg/api/api.go index 853e9b3..de41d17 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -4,6 +4,7 @@ package api import ( + "errors" "fmt" "net/http" "time" @@ -188,3 +189,52 @@ 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.License + 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 { + err := errors.New("") + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("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 + } + + if err := query.Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("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/authenticate/auth.go b/pkg/authenticate/auth.go new file mode 100644 index 0000000..2339e2b --- /dev/null +++ b/pkg/authenticate/auth.go @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2023 Kavya Shukla +// SPDX-License-Identifier: GPL-2.0-only + +package authenticate + +import ( + "encoding/base64" + "errors" + "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 + err := c.ShouldBindJSON(&user) + if err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("invalid request"), + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + + result := api.DB.Create(&user) + if result.Error != nil { + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: fmt.Sprintf("internal Server Error"), + 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.StatusOK, + Meta: models.Meta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetAllUser(c *gin.Context) { + var users []models.User + err := api.DB.Find(&users).Error + if err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("invalid request"), + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + res := models.UserResponse{ + Data: users, + Status: http.StatusOK, + Meta: models.Meta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetUser(c *gin.Context) { + var user models.User + id := c.Param("id") + err := api.DB.Where("id = ?", id).First(&user).Error + if err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("invalid request"), + 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 == "" { + err := errors.New("authentication failed") + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("Please check your credentials and try again"), + Error: err.Error(), + 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: fmt.Sprintf("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 + result := api.DB.Where("username = ?", username).First(&user) + if result.Error != nil { + er := models.LicenseError{ + Status: http.StatusUnauthorized, + Message: fmt.Sprintf("User name not found"), + Error: result.Error.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: fmt.Sprintf("Incorrect password"), + Error: result.Error.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/models/types.go b/pkg/models/types.go index 346ecb2..3b570ca 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"` + Username string `json:"username" gorm:"unique"` + Userlevel string `json:"userlevel" gorm:"unique"` + Userpassword string `json:"userpassword" gorm:"unique"` +} + +// 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"` +}