Skip to content

Commit

Permalink
feat: added authentication in API and search api
Browse files Browse the repository at this point in the history
	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 <[email protected]>
  • Loading branch information
k-avy committed Jul 19, 2023
1 parent dbc69bc commit 750c059
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 5 deletions.
17 changes: 13 additions & 4 deletions cmd/laas/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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()
}
48 changes: 48 additions & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

}
188 changes: 188 additions & 0 deletions pkg/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// SPDX-FileCopyrightText: 2023 Kavya Shukla <[email protected]>
// 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()
}
}
2 changes: 1 addition & 1 deletion pkg/authenticate/doc.go → pkg/auth/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
// SPDX-License-Identifier: GPL-2.0-only

// Package authenticate is build to authenticate the API.
package authenticate
package auth
15 changes: 15 additions & 0 deletions pkg/models/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

0 comments on commit 750c059

Please sign in to comment.