Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions cmd/laas/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: 2023 Siemens AG
// SPDX-FileContributor: Gaurav Mishra <[email protected]>
// SPDX-FileContributor: Dearsh Oberoi <[email protected]>
// SPDX-FileContributor: 2025 Chayan Das <[email protected]>
//
// SPDX-License-Identifier: GPL-2.0-only

Expand All @@ -12,16 +13,21 @@ import (
"flag"
"log"
"os"
"strconv"

"github.com/joho/godotenv"
"github.com/lestrrat-go/httprc/v3"
"github.com/lestrrat-go/jwx/v3/jwk"
"go.uber.org/zap"

_ "github.com/dave/jennifer/jen"
_ "github.com/fossology/LicenseDb/cmd/laas/docs"
"github.com/fossology/LicenseDb/pkg/api"
"github.com/fossology/LicenseDb/pkg/auth"
"github.com/fossology/LicenseDb/pkg/db"
"github.com/fossology/LicenseDb/pkg/email"
logger "github.com/fossology/LicenseDb/pkg/log"

"github.com/fossology/LicenseDb/pkg/utils"
"github.com/fossology/LicenseDb/pkg/validations"
)
Expand All @@ -39,21 +45,28 @@ func main() {
if err != nil {
log.Fatalf("Error loading .env file")
}

flag.Parse()

// Start the email service
EnableSMTP, _ := strconv.ParseBool(os.Getenv("ENABLE_SMTP"))
if EnableSMTP {
if err := email.Init(); err != nil {
logger.LogFatal("Failed to initialize email service", zap.Error(err))
}
}

if os.Getenv("TOKEN_HOUR_LIFESPAN") == "" || os.Getenv("API_SECRET") == "" || os.Getenv("DEFAULT_ISSUER") == "" {
log.Fatal("Mandatory environment variables not configured")
logger.LogFatal("Mandatory environment variables not configured")
}

if os.Getenv("JWKS_URI") != "" {
cache, err := jwk.NewCache(context.Background(), httprc.NewClient())
if err != nil {
log.Fatalf("Failed to create a jwk.Cache from the oidc provider's URL: %s", err)
logger.LogFatal("Failed to create a jwk.Cache from the oidc provider's URL:", zap.Error(err))
}

if err := cache.Register(context.Background(), os.Getenv("JWKS_URI")); err != nil {
log.Fatalf("Failed to create a jwk.Cache from the oidc provider's URL: %s", err)
logger.LogFatal("Failed to create a jwk.Cache from the oidc provider's URL:", zap.Error(err))
}

auth.Jwks = cache
Expand All @@ -76,8 +89,7 @@ func main() {
}

r := api.Router()

if err := r.Run(); err != nil {
log.Fatalf("Error while running the server: %v", err)
logger.LogFatal("Error while running the server:", zap.Error(err))
}
}
14 changes: 13 additions & 1 deletion configs/.env.dev.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
# SPDX-FileCopyrightText: FOSSology contributors
#SPDX-FileContributor: 2025 Chayan Das <[email protected]>

# How long the token can be valid
TOKEN_HOUR_LIFESPAN=24
Expand Down Expand Up @@ -49,4 +50,15 @@ DB_HOST=localhost
# This value can be adjusted based on the requirements of the similarity search
# A lower value will result in more matches, while a higher value will be more strict
# Default is set to 0.7, but can be changed to a higher value like 0.8 or 0.9 for stricter matching
SIMILARITY_THRESHOLD = 0.8
SIMILARITY_THRESHOLD = 0.8


# SMTP Configuration
ENABLE_SMTP=false
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
[email protected]
SMTP_PASSWORD=your_password
[email protected]


15 changes: 14 additions & 1 deletion configs/.env.test.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,17 @@ DB_HOST=localhost
# This value can be adjusted based on the requirements of the similarity search
# A lower value will result in more matches, while a higher value will be more strict
# Default is set to 0.7, but can be changed to a higher value like 0.8 or 0.9 for stricter matching
SIMILARITY_THRESHOLD = 0.8
SIMILARITY_THRESHOLD = 0.8




# SMTP Configuration
ENABLE_SMTP=false
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
[email protected]
SMTP_PASSWORD=your_password
[email protected]


2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ require (
github.com/lib/pq v1.10.9 // indirect
github.com/segmentio/asm v1.2.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/sync v0.12.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
52 changes: 49 additions & 3 deletions pkg/api/licenses.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import (
"time"

"github.com/fossology/LicenseDb/pkg/db"
email "github.com/fossology/LicenseDb/pkg/email"
logger "github.com/fossology/LicenseDb/pkg/log"
"github.com/fossology/LicenseDb/pkg/models"
"github.com/fossology/LicenseDb/pkg/utils"
"github.com/fossology/LicenseDb/pkg/validations"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"

"gorm.io/gorm"
)

Expand Down Expand Up @@ -308,7 +309,15 @@ func CreateLicense(c *gin.Context) {
c.JSON(http.StatusInternalServerError, er)
return err
}

// send email
if email.Email != nil && email.Email.IsRunning() {
userName := *lic.User.UserName
userEmail := *lic.User.UserEmail
shortName := *lic.Shortname
email.NotifyLicenseCreated(userEmail, userName, shortName)
} else {
logger.LogInfo("SMTP disabled or not reachable. Skipping email.")
}
res := models.LicenseResponse{
Data: []models.LicenseResponseDTO{lic.ConvertToLicenseResponseDTO()},
Status: http.StatusCreated,
Expand All @@ -321,6 +330,7 @@ func CreateLicense(c *gin.Context) {

return nil
})

}

// UpdateLicense Update license with given shortname and create audit and changelog entries.
Expand Down Expand Up @@ -451,6 +461,14 @@ func UpdateLicense(c *gin.Context) {
c.JSON(http.StatusInternalServerError, er)
return err
}
if email.Email != nil && email.Email.IsRunning() {
userName := *newLicense.User.UserName
userEmail := *newLicense.User.UserEmail
shortName := *newLicense.Shortname
email.NotifyLicenseUpdated(userEmail, userName, shortName)
} else {
logger.LogInfo("SMTP disabled or not reachable. Skipping email.")
}

res := models.LicenseResponse{
Data: []models.LicenseResponseDTO{newLicense.ConvertToLicenseResponseDTO()},
Expand Down Expand Up @@ -573,6 +591,7 @@ func SearchInLicense(c *gin.Context) {
// @Router /licenses/import [post]
func ImportLicenses(c *gin.Context) {
userId := c.MustGet("userId").(int64)
var user models.User
file, header, err := c.Request.FormFile("file")
if err != nil {
er := models.LicenseError{
Expand Down Expand Up @@ -641,8 +660,21 @@ func ImportLicenses(c *gin.Context) {
res := models.ImportLicensesResponse{
Status: http.StatusOK,
}

var total, success, failed int
err = db.DB.Where(models.User{Id: userId}).First(&user).Error
if err != nil {
er := models.LicenseError{
Status: http.StatusNotFound,
Message: fmt.Sprintf("no with userId '%d' exists", userId),
Error: err.Error(),
Path: c.Request.URL.Path,
Timestamp: time.Now().Format(time.RFC3339),
}
c.JSON(http.StatusNotFound, er)
return
}
for i := range licenses {
total++
lic, err := licenses[i].ConvertToLicenseDB()
if err != nil {
res.Data = append(res.Data, models.LicenseError{
Expand Down Expand Up @@ -683,6 +715,20 @@ func ImportLicenses(c *gin.Context) {
})
// error is not returned here as it will rollback the transaction
}
if importStatus == utils.IMPORT_LICENSE_CREATED ||
importStatus == utils.IMPORT_LICENSE_UPDATED ||
importStatus == utils.IMPORT_LICENSE_UPDATED_EXCEPT_TEXT {
success++
} else {
failed++
}
}
if email.Email != nil && email.Email.IsRunning() {
userName := *user.UserName
userEmail := *user.UserEmail
email.NotifyImportSummary(userEmail, userName, "Licenses", total, success, failed)
} else {
logger.LogInfo("SMTP disabled or not reachable. Skipping email.")
}

c.JSON(http.StatusOK, res)
Expand Down
23 changes: 23 additions & 0 deletions pkg/email/admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2025 Chayan Das <[email protected]>
// SPDX-License-Identifier: GPL-2.0-only

package email

import (
"github.com/fossology/LicenseDb/pkg/db"
"github.com/fossology/LicenseDb/pkg/models"
)

func FetchAdminEmails() ([]string, error) {
var emails []string
admin := "ADMIN"
err := db.DB.
Model(&models.User{}).
Where(&models.User{UserLevel: &admin}). // can add super_admin too
Pluck("user_email", &emails).Error
if err != nil {
return nil, err
}

return emails, nil
}
Loading
Loading