Skip to content

Commit e6109dd

Browse files
feat: add certificate deletion API with validation rules (#688)
* feat: add certificate deletion API with validation rules Implements DELETE /api/v1/amt/certificates/{guid}/{instanceId} with: - Profile association validation - Read-only certificate protection - Full layer implementation (HTTP -> usecase -> WSMAN) Closes: #567 Signed-off-by: ShradhaGupta31 <shradha.gupta@intel.com> * refactor: Rename DeletePublicCert to DeleteCertificate for consistency - Rename Management interface method from DeletePublicCert to DeleteCertificate - Update ConnectionEntry implementation method name to match interface - Update all test references and comments to use consistent naming - Align method naming with REST API endpoints and use case methods This change ensures consistent naming convention across the codebase where all certificate deletion operations use the "DeleteCertificate" naming pattern instead of the inconsistent "DeletePublicCert". Signed-off-by: ShradhaGupta31 <shradha.gupta@intel.com> --------- Signed-off-by: ShradhaGupta31 <shradha.gupta@intel.com>
1 parent 11a704a commit e6109dd

File tree

12 files changed

+434
-3
lines changed

12 files changed

+434
-3
lines changed

internal/controller/http/v1/certs.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,17 @@ func (r *deviceManagementRoutes) addCertificate(c *gin.Context) {
5353

5454
c.JSON(http.StatusOK, handle)
5555
}
56+
57+
func (r *deviceManagementRoutes) deleteCertificate(c *gin.Context) {
58+
guid := c.Param("guid")
59+
instanceID := c.Param("instanceId")
60+
61+
err := r.d.DeleteCertificate(c.Request.Context(), guid, instanceID)
62+
if err != nil {
63+
ErrorResponse(c, err)
64+
65+
return
66+
}
67+
68+
c.JSON(http.StatusOK, gin.H{"message": "Certificate deleted successfully"})
69+
}

internal/controller/http/v1/devicemanagement.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func NewAmtRoutes(handler *gin.RouterGroup, d devices.Feature, amt amtexplorer.F
5757

5858
h.GET("certificates/:guid", r.getCertificates)
5959
h.POST("certificates/:guid", r.addCertificate)
60+
h.DELETE("certificates/:guid/:instanceId", r.deleteCertificate)
6061

6162
// KVM display settings
6263
h.GET("kvm/displays/:guid", r.getKVMDisplays)

internal/controller/http/v1/devicemanagement_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"github.com/device-management-toolkit/console/internal/entity/dto/v1"
1919
dtov2 "github.com/device-management-toolkit/console/internal/entity/dto/v2"
2020
"github.com/device-management-toolkit/console/internal/mocks"
21+
"github.com/device-management-toolkit/console/internal/usecase/devices"
22+
"github.com/device-management-toolkit/console/pkg/consoleerrors"
2123
"github.com/device-management-toolkit/console/pkg/logger"
2224
)
2325

@@ -401,6 +403,49 @@ func TestDeviceManagement(t *testing.T) {
401403
expectedCode: http.StatusInternalServerError,
402404
response: nil,
403405
},
406+
{
407+
name: "deleteCertificate - successful deletion",
408+
url: "/api/v1/amt/certificates/valid-guid/Intel%28r%29%20AMT%20Certificate%3A%20Handle%3A%201",
409+
method: http.MethodDelete,
410+
mock: func(m *mocks.MockDeviceManagementFeature) {
411+
m.EXPECT().DeleteCertificate(
412+
context.Background(),
413+
"valid-guid",
414+
"Intel(r) AMT Certificate: Handle: 1",
415+
).Return(nil)
416+
},
417+
expectedCode: http.StatusOK,
418+
response: gin.H{"message": "Certificate deleted successfully"},
419+
},
420+
{
421+
name: "deleteCertificate - device not found",
422+
url: "/api/v1/amt/certificates/invalid-guid/Intel%28r%29%20AMT%20Certificate%3A%20Handle%3A%201",
423+
method: http.MethodDelete,
424+
mock: func(m *mocks.MockDeviceManagementFeature) {
425+
m.EXPECT().DeleteCertificate(
426+
context.Background(),
427+
"invalid-guid",
428+
"Intel(r) AMT Certificate: Handle: 1",
429+
).Return(devices.ErrNotFound)
430+
},
431+
expectedCode: http.StatusNotFound,
432+
response: nil,
433+
},
434+
{
435+
name: "deleteCertificate - certificate is read-only",
436+
url: "/api/v1/amt/certificates/valid-guid/Intel%28r%29%20AMT%20Certificate%3A%20Handle%3A%200",
437+
method: http.MethodDelete,
438+
mock: func(m *mocks.MockDeviceManagementFeature) {
439+
validationErr := dto.NotValidError{Console: consoleerrors.CreateConsoleError("DeleteCertificate")}
440+
m.EXPECT().DeleteCertificate(
441+
context.Background(),
442+
"valid-guid",
443+
"Intel(r) AMT Certificate: Handle: 0",
444+
).Return(validationErr)
445+
},
446+
expectedCode: http.StatusBadRequest,
447+
response: nil,
448+
},
404449
}
405450

406451
for _, tc := range tests {

internal/controller/ws/v1/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type Feature interface {
6161
GetDiskInfo(c context.Context, guid string) (dto.DiskInfo, error)
6262
GetDeviceCertificate(c context.Context, guid string) (dto.Certificate, error)
6363
AddCertificate(c context.Context, guid string, certInfo dto.CertInfo) (string, error)
64+
DeleteCertificate(c context.Context, guid, instanceID string) error
6465
GetBootSourceSetting(ctx context.Context, guid string) ([]dto.BootSources, error)
6566
// KVM Screen Settings
6667
GetKVMScreenSettings(c context.Context, guid string) (dto.KVMScreenSettings, error)

internal/mocks/devicemanagement_mocks.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/mocks/wsman_mocks.go

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/mocks/wsv1_mocks.go

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/usecase/devices/certificates.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/device-management-toolkit/console/internal/entity/dto/v1"
2525
"github.com/device-management-toolkit/console/internal/usecase/devices/wsman"
26+
"github.com/device-management-toolkit/console/pkg/consoleerrors"
2627
)
2728

2829
const (
@@ -31,6 +32,12 @@ const (
3132
TypeWired string = "Wired"
3233
)
3334

35+
var (
36+
ErrCertificateNotFound = fmt.Errorf("certificate not found")
37+
ErrCertificateAssociatedProfiles = fmt.Errorf("certificate is associated with profiles")
38+
ErrCertificateReadOnly = fmt.Errorf("certificate is read-only")
39+
)
40+
3441
func processConcreteDependencies(certificateHandle string, profileAssociation *dto.ProfileAssociation, dependencyItems []concrete.ConcreteDependency, securitySettings dto.SecuritySettings) {
3542
for i := range dependencyItems {
3643
di := dependencyItems[i]
@@ -379,3 +386,59 @@ func (uc *UseCase) AddCertificate(c context.Context, guid string, certInfo dto.C
379386

380387
return handle, nil
381388
}
389+
390+
func (uc *UseCase) DeleteCertificate(c context.Context, guid, instanceID string) error {
391+
item, err := uc.repo.GetByID(c, guid, "")
392+
if err != nil {
393+
return err
394+
}
395+
396+
if item == nil || item.GUID == "" {
397+
return ErrNotFound
398+
}
399+
400+
// First, get all certificates to check if the certificate to delete is associated with any profiles
401+
securitySettings, err := uc.GetCertificates(c, guid)
402+
if err != nil {
403+
return err
404+
}
405+
406+
// Find the certificate to be deleted
407+
var targetCert *dto.RefinedCertificate
408+
409+
for i := range securitySettings.CertificateResponse.Certificates {
410+
if securitySettings.CertificateResponse.Certificates[i].InstanceID == instanceID {
411+
targetCert = &securitySettings.CertificateResponse.Certificates[i]
412+
413+
break
414+
}
415+
}
416+
417+
if targetCert == nil {
418+
return ErrNotFound.Wrap("DeleteCertificate", "certificate not found", ErrCertificateNotFound)
419+
}
420+
421+
// Check if the certificate is associated with any profiles
422+
if len(targetCert.AssociatedProfiles) > 0 {
423+
validationErr := dto.NotValidError{Console: consoleerrors.CreateConsoleError("DeleteCertificate")}
424+
425+
return validationErr.Wrap("DeleteCertificate", "certificate associated with profiles", ErrCertificateAssociatedProfiles)
426+
}
427+
428+
// Check if the certificate is read-only (cannot be deleted)
429+
if targetCert.ReadOnlyCertificate {
430+
validationErr := dto.NotValidError{Console: consoleerrors.CreateConsoleError("DeleteCertificate")}
431+
432+
return validationErr.Wrap("DeleteCertificate", "certificate is read-only", ErrCertificateReadOnly)
433+
}
434+
435+
// If the certificate is not associated with any profiles and is not read-only, proceed with deletion
436+
device := uc.device.SetupWsmanClient(*item, false, true)
437+
438+
err = device.DeleteCertificate(instanceID)
439+
if err != nil {
440+
return ErrDeviceUseCase.Wrap("DeleteCertificate", "failed to delete certificate", fmt.Errorf("failed to delete certificate %s: %w", instanceID, err))
441+
}
442+
443+
return nil
444+
}

0 commit comments

Comments
 (0)