Skip to content

Commit

Permalink
Merge pull request #12 from k-avy/kavya/api/task3
Browse files Browse the repository at this point in the history
feat: added authentication in API and search endpoint

Reviewed by: [email protected], [email protected]
Tested by: [email protected], [email protected]
  • Loading branch information
ag4ums authored Aug 8, 2023
2 parents dbc69bc + c6c6d4e commit 7c324a9
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 5 deletions.
18 changes: 14 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,15 @@ 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())
r.GET("/api/licenses/:shortname", api.GetLicense)
authorized.POST("/api/licenses", api.CreateLicense)
authorized.PATCH("/api/licenses/:shortname", api.UpdateLicense)
r.GET("/api/licenses", api.FilterLicense)
r.POST("/api/licenses/search", api.SearchInLicense)
authorized.POST("/api/users", auth.CreateUser)
authorized.GET("/api/users", auth.GetAllUser)
authorized.GET("/api/users/:id", auth.GetUser)
r.Run()
}
124 changes: 124 additions & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,127 @@ func UpdateLicense(c *gin.Context) {
c.JSON(http.StatusOK, res)

}

func FilterLicense(c *gin.Context) {
SpdxId := c.Query("spdxid")
DetectorType := c.Query("detector_type")
GPLv2compatible := c.Query("gplv2compatible")
GPLv3compatible := c.Query("gplv3compatible")
marydone := c.Query("marydone")
active := c.Query("active")
OSIapproved := c.Query("osiapproved")
fsffree := c.Query("fsffree")
copyleft := c.Query("copyleft")
var license []models.LicenseDB
query := DB.Model(&license)

if SpdxId == "" && GPLv2compatible == "" && GPLv3compatible == "" && DetectorType == "" && marydone == "" && active == "" && fsffree == "" && OSIapproved == "" && copyleft == "" {
GetAllLicense(c)
return
}
if active != "" {
query = query.Where("active=?", active)
}

if fsffree != "" {
query = query.Where("fs_ffree=?", fsffree)
}

if OSIapproved != "" {
query = query.Where("os_iapproved=?", OSIapproved)
}

if copyleft != "" {
query = query.Where("copyleft=?", copyleft)
}

if SpdxId != "" {
query = query.Where("spdx_id=?", SpdxId)
}

if DetectorType != "" {
query = query.Where("detector_type=?", DetectorType)
}

if GPLv2compatible != "" {
query = query.Where("gp_lv2compatible=?", GPLv2compatible)
}

if GPLv3compatible != "" {
query = query.Where("gp_lv3compatible=?", GPLv3compatible)
}

if marydone != "" {
query = query.Where("marydone=?", marydone)
}

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
}
query.Find(&license)

res := models.LicenseResponse{
Data: license,
Status: http.StatusOK,
Meta: models.Meta{
ResourceCount: len(license),
},
}
c.JSON(http.StatusOK, res)

}

func SearchInLicense(c *gin.Context) {
var input models.SearchLicense

if err := c.ShouldBindJSON(&input); 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
}

var license []models.LicenseDB
query := DB.Model(&license)

if input.SearchType == "fuzzy" {
query = query.Where(fmt.Sprintf("%s ILIKE ?", input.Field), fmt.Sprintf("%%%s%%", input.SearchTerm))
} else if input.SearchType == "" || input.SearchType == "full_text_search" {
query = query.Where(input.Field+" @@ plainto_tsquery(?)", input.SearchTerm)

} 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
}
query.Find(&license)

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
21 changes: 21 additions & 0 deletions pkg/models/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,24 @@ 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"`
}

type SearchLicense struct {
Field string `json:"field" binding:"required"`
SearchTerm string `json:"search_term" binding:"required"`
SearchType string `json:"search_type"`
}

0 comments on commit 7c324a9

Please sign in to comment.