diff --git a/cmd/devguard/main.go b/cmd/devguard/main.go index 2c091ade5..ef1653942 100644 --- a/cmd/devguard/main.go +++ b/cmd/devguard/main.go @@ -155,6 +155,7 @@ func main() { fx.Invoke(func(FalsePositiveRuleRouter router.VEXRuleRouter) {}), fx.Invoke(func(ExternalReferenceRouter router.ExternalReferenceRouter) {}), fx.Invoke(func(CrowdsourcedVexingRouter router.CrowdsourcedVexingRouter) {}), + fx.Invoke(func(AdvisoryRouter router.AdvisoryRouter) {}), fx.Invoke(func(lc fx.Lifecycle, encryptionService shared.DBEncryptionService) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { diff --git a/controllers/advisory_controller.go b/controllers/advisory_controller.go new file mode 100644 index 000000000..6611147a7 --- /dev/null +++ b/controllers/advisory_controller.go @@ -0,0 +1,51 @@ +// Copyright (C) 2023 Tim Bastin, l3montree GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package controllers + +import ( + "github.com/l3montree-dev/devguard/dtos" + "github.com/l3montree-dev/devguard/shared" + "github.com/labstack/echo/v4" + "golang.org/x/exp/slog" +) + +type AdvisoryController struct { + advisoryService shared.AdvisoryService +} + +func NewAdvisoryController(advisoryService shared.AdvisoryService) *AdvisoryController { + return &AdvisoryController{ + advisoryService: advisoryService, + } +} + +func (controller *AdvisoryController) CreateName(ctx shared.Context) error { + slog.Info("test") + var req dtos.AdvisoryCreateName + if err := ctx.Bind(&req); err != nil { + return echo.NewHTTPError(400, "unable to process request").WithInternal(err) + } + + newName := req.Name + + err := controller.advisoryService.CreateName(ctx.Request().Context(), newName) + + if err != nil { + return echo.NewHTTPError(409, "could not set name").WithInternal(err) + } + + return ctx.JSON(200, newName) +} diff --git a/controllers/providers.go b/controllers/providers.go index b7d5696cf..df0b40cf5 100644 --- a/controllers/providers.go +++ b/controllers/providers.go @@ -122,4 +122,6 @@ var ControllerModule = fx.Options( //Crowdsourced Vexing fx.Provide(NewCrowdsourcedVexingController), + + fx.Provide(NewAdvisoryController), ) diff --git a/database/migrations/20260623101120_add_advisory_table.up.sql b/database/migrations/20260623101120_add_advisory_table.up.sql new file mode 100644 index 000000000..8d361fea0 --- /dev/null +++ b/database/migrations/20260623101120_add_advisory_table.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE public.advisories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW(), + advisory_name TEXT NOT NULL +) \ No newline at end of file diff --git a/database/models/advisory_model.go b/database/models/advisory_model.go new file mode 100644 index 000000000..340021711 --- /dev/null +++ b/database/models/advisory_model.go @@ -0,0 +1,10 @@ +package models + +type Advisory struct { + Model + AdvisoryName string `json:"name" gorm:"type:text;column:advisory_name"` +} + +func (m Advisory) TableName() string { + return "advisories" +} diff --git a/database/repositories/advisory_repository.go b/database/repositories/advisory_repository.go new file mode 100644 index 000000000..62c448afd --- /dev/null +++ b/database/repositories/advisory_repository.go @@ -0,0 +1,33 @@ +package repositories + +import ( + "context" + + "github.com/google/uuid" + "github.com/l3montree-dev/devguard/database/models" + "github.com/l3montree-dev/devguard/shared" + "github.com/l3montree-dev/devguard/utils" + "gorm.io/gorm" +) + +type AdvisoryRepository struct { + db *gorm.DB + utils.Repository[uuid.UUID, models.Advisory, *gorm.DB] +} + +func NewAdvisoryRepository(db *gorm.DB) *AdvisoryRepository { + return &AdvisoryRepository{ + db: db, + Repository: newGormRepository[uuid.UUID, models.Advisory](db), + } +} + +var _ shared.AdvisoryRepository = (*AdvisoryRepository)(nil) + +func (advisoryRepository *AdvisoryRepository) CreateName(ctx context.Context, tx *gorm.DB, name string) error { + err := advisoryRepository.GetDB(ctx, tx).Create(&models.Advisory{AdvisoryName: name}).Error + if err != nil { + return err + } + return nil +} diff --git a/database/repositories/providers.go b/database/repositories/providers.go index 84d540a31..fd9cc645c 100644 --- a/database/repositories/providers.go +++ b/database/repositories/providers.go @@ -59,4 +59,5 @@ var Module = fx.Options( fx.Provide(fx.Annotate(NewTrustedEntityRepository, fx.As(new(shared.TrustedEntityRepository)))), fx.Provide(fx.Annotate(NewDependencyProxyRepository, fx.As(new(shared.DependencyProxySecretRepository)))), fx.Provide(fx.Annotate(NewAdminRepository, fx.As(new(shared.AdminRepository)))), + fx.Provide(fx.Annotate(NewAdvisoryRepository, fx.As(new(shared.AdvisoryRepository)))), ) diff --git a/dtos/advisory_dto.go b/dtos/advisory_dto.go new file mode 100644 index 000000000..7242f601f --- /dev/null +++ b/dtos/advisory_dto.go @@ -0,0 +1,24 @@ +// Copyright (C) 2023 Tim Bastin, l3montree GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package dtos + +type AdvisoryCreateName struct { + Name string `json:"name" validate:"required"` +} + +type AdvisoryDTO struct { + Name string `json:"name"` +} diff --git a/router/advisory_router.go b/router/advisory_router.go new file mode 100644 index 000000000..e8efd528e --- /dev/null +++ b/router/advisory_router.go @@ -0,0 +1,38 @@ +// Copyright (C) 2024 Tim Bastin, l3montree GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package router + +import ( + "github.com/l3montree-dev/devguard/controllers" + "github.com/l3montree-dev/devguard/shared" + "github.com/labstack/echo/v4" +) + +type AdvisoryRouter struct { + *echo.Group +} + +func NewAdvisoryRouter( + assetRepository shared.AssetRepository, + assetVersionGroup AssetVersionRouter, + advisoryController *controllers.AdvisoryController, +) AdvisoryRouter { + advisoryRouter := assetVersionGroup.Group.Group("/advisory") + advisoryRouter.POST("/submit/", advisoryController.CreateName) + //advisoryRouter.GET("/", advisoryController.ReadName) + + return AdvisoryRouter{Group: advisoryRouter} +} diff --git a/router/providers.go b/router/providers.go index 7d5056903..d0624d0b7 100644 --- a/router/providers.go +++ b/router/providers.go @@ -22,4 +22,5 @@ var RouterModule = fx.Options( fx.Provide(NewVEXRuleRouter), fx.Provide(NewExternalReferenceRouter), fx.Provide(NewCrowdsourcedVexingRouter), + fx.Provide(NewAdvisoryRouter), ) diff --git a/services/advisory_service.go b/services/advisory_service.go new file mode 100644 index 000000000..d77179218 --- /dev/null +++ b/services/advisory_service.go @@ -0,0 +1,23 @@ +package services + +import ( + "context" + + "github.com/l3montree-dev/devguard/shared" +) + +type AdvisoryService struct { + advisoryRepository shared.AdvisoryRepository +} + +func NewAdvisoryService(advisoryRepository shared.AdvisoryRepository) *AdvisoryService { + return &AdvisoryService{ + advisoryRepository: advisoryRepository, + } +} + +var _ shared.AdvisoryService = (*AdvisoryService)(nil) + +func (s *AdvisoryService) CreateName(ctx context.Context, name string) error { + return s.advisoryRepository.CreateName(ctx, nil, name) +} diff --git a/services/providers.go b/services/providers.go index 54683f373..6ea5b6792 100644 --- a/services/providers.go +++ b/services/providers.go @@ -39,4 +39,5 @@ var ServiceModule = fx.Options( fx.Provide(fx.Annotate(NewAdminService, fx.As(new(shared.AdminService)))), fx.Provide(fx.Annotate(NewCrowdsourcedVexingService, fx.As(new(shared.CrowdSourcedVexingService)))), fx.Provide(fx.Annotate(NewDBEncryptionService, fx.As(new(shared.DBEncryptionService)))), + fx.Provide(fx.Annotate(NewAdvisoryService, fx.As(new(shared.AdvisoryService)))), ) diff --git a/shared/common_interfaces.go b/shared/common_interfaces.go index c6ff223a3..ed2ade03b 100644 --- a/shared/common_interfaces.go +++ b/shared/common_interfaces.go @@ -761,6 +761,14 @@ type RBACProvider interface { GetAllUsers() ([]string, error) } +type AdvisoryService interface { + CreateName(ctx context.Context, name string) error +} + +type AdvisoryRepository interface { + CreateName(ctx context.Context, tx DB, name string) error +} + type RBACMiddleware = func(obj Object, act Action) echo.MiddlewareFunc type Role string