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
15 changes: 8 additions & 7 deletions internal/entity/dto/v1/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ type Device struct {
}

type DeviceInfo struct {
FWVersion string `json:"fwVersion"`
FWBuild string `json:"fwBuild"`
FWSku string `json:"fwSku"`
CurrentMode string `json:"currentMode"`
Features string `json:"features"`
IPAddress string `json:"ipAddress"`
LastUpdated time.Time `json:"lastUpdated"`
FWVersion string `json:"fwVersion"`
FWBuild string `json:"fwBuild"`
FWSku string `json:"fwSku"`
CurrentMode string `json:"currentMode"`
Features string `json:"features"`
IPAddress string `json:"ipAddress"`
LastUpdated time.Time `json:"lastUpdated"`
LMSInstalled *bool `json:"lmsInstalled,omitempty"`
}

type Explorer struct {
Expand Down
46 changes: 46 additions & 0 deletions internal/mocks/crypto/crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Package crypto provides a lightweight fake implementation of
// security.Cryptor for use in tests. It lives in its own leaf package (no
// dependency on internal/usecase/devices) so it can be imported by both
// internal (package foo) and external (package foo_test) test files without
// creating an import cycle through internal/mocks.
package crypto

import (
"gopkg.in/yaml.v2"

"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/config"
)

type MockCrypto struct{}

const encryptedData = "encrypted"

// Encrypt encrypts a string.
func (c MockCrypto) Encrypt(_ string) (string, error) {
return encryptedData, nil
}

// EncryptWithKey encrypts a string with the provided key.
func (c MockCrypto) EncryptWithKey(_, _ string) (string, error) {
return encryptedData, nil
}

func (c MockCrypto) GenerateKey() string {
return "key"
}

func (c MockCrypto) Decrypt(_ string) (string, error) {
return "decrypted", nil
}

// ReadAndDecryptFile reads encrypted data from a file and decrypts it.
func (c MockCrypto) ReadAndDecryptFile(_ string) (config.Configuration, error) {
var configuration config.Configuration

err := yaml.Unmarshal([]byte(""), &configuration)
if err != nil {
return config.Configuration{}, err
}

return configuration, nil
}
42 changes: 6 additions & 36 deletions internal/mocks/crypto_mocks.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,11 @@
package mocks

import (
"gopkg.in/yaml.v2"

"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/config"
crypto "github.com/device-management-toolkit/console/internal/mocks/crypto"
)

type MockCrypto struct{}

const encryptedData = "encrypted"

// Encrypt encrypts a string.
func (c MockCrypto) Encrypt(_ string) (string, error) {
return encryptedData, nil
}

// Encrypt encrypts a string.
func (c MockCrypto) EncryptWithKey(_, _ string) (string, error) {
return encryptedData, nil
}

func (c MockCrypto) GenerateKey() string {
return "key"
}

func (c MockCrypto) Decrypt(_ string) (string, error) {
return "decrypted", nil
}

// Read encrypted data from file and decrypt it.
func (c MockCrypto) ReadAndDecryptFile(_ string) (config.Configuration, error) {
var configuration config.Configuration

err := yaml.Unmarshal([]byte(""), &configuration)
if err != nil {
return config.Configuration{}, err
}

return configuration, nil
}
// MockCrypto is a fake security.Cryptor for tests. The implementation lives in
// the internal/mocks/crypto leaf package so it can be shared with internal
// (white-box) test files without an import cycle; this alias preserves the
// existing mocks.MockCrypto call sites.
type MockCrypto = crypto.MockCrypto
40 changes: 34 additions & 6 deletions internal/usecase/devices/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ func (uc *UseCase) Get(ctx context.Context, top, skip int, tenantID string) ([]d

for i := range data {
tmpEntity := data[i] // create a new variable to avoid memory aliasing
d1[i] = *uc.entityToDTO(&tmpEntity)

d, err := uc.entityToDTO(&tmpEntity)
if err != nil {
return nil, err
}

d1[i] = *d
}

return d1, nil
Expand All @@ -58,7 +64,13 @@ func (uc *UseCase) GetByColumn(ctx context.Context, columnName, queryValue, tena

for i := range data {
tmpEntity := data[i] // create a new variable to avoid memory aliasing
d1[i] = *uc.entityToDTO(&tmpEntity)

d, err := uc.entityToDTO(&tmpEntity)
if err != nil {
return nil, err
}

d1[i] = *d
}

return d1, nil
Expand All @@ -74,7 +86,10 @@ func (uc *UseCase) GetByID(ctx context.Context, guid, tenantID string, includeSe
return nil, ErrNotFound
}

d2 := uc.entityToDTO(data)
d2, err := uc.entityToDTO(data)
if err != nil {
return nil, err
}

if includeSecrets {
if err := uc.decryptSecrets(d2, data); err != nil {
Expand Down Expand Up @@ -140,7 +155,13 @@ func (uc *UseCase) GetByTags(ctx context.Context, tags, method string, limit, of

for i := range data {
tmpEntity := data[i] // create a new variable to avoid memory aliasing
d1[i] = *uc.entityToDTO(&tmpEntity)

d, err := uc.entityToDTO(&tmpEntity)
if err != nil {
return nil, err
}

d1[i] = *d
}

return d1, nil
Expand Down Expand Up @@ -209,7 +230,10 @@ func (uc *UseCase) Update(ctx context.Context, d *dto.Device, fields map[string]
return nil, err
}

d2 := uc.entityToDTO(updateDevice)
d2, err := uc.entityToDTO(updateDevice)
if err != nil {
return nil, err
}

// invalidate connection cache
uc.device.DestroyWsmanClient(*d2)
Expand Down Expand Up @@ -237,7 +261,11 @@ func (uc *UseCase) Insert(ctx context.Context, d *dto.Device) (*dto.Device, erro
return nil, err
}

d2 := uc.entityToDTO(newDevice)
d2, err := uc.entityToDTO(newDevice)
if err != nil {
return nil, err
}

if newDevice.Tags == "" {
d2.Tags = []string{}
}
Expand Down
11 changes: 9 additions & 2 deletions internal/usecase/devices/transform_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,15 @@ func verifyDTOToEntity(t *testing.T, uc *UseCase, guid, certHash string, buildDT
func verifyEntityToDTO(t *testing.T, uc *UseCase, buildEntity func() *entity.Device) {
t.Helper()

firstDTO := uc.entityToDTO(buildEntity())
secondDTO := uc.entityToDTO(buildEntity())
firstDTO, err := uc.entityToDTO(buildEntity())
if err != nil {
t.Fatalf("entityToDTO first call: %v", err)
}

secondDTO, err := uc.entityToDTO(buildEntity())
if err != nil {
t.Fatalf("entityToDTO second call: %v", err)
}

if !reflect.DeepEqual(firstDTO, secondDTO) {
t.Fatalf("entityToDTO result mismatch")
Expand Down
66 changes: 50 additions & 16 deletions internal/usecase/devices/usecase.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package devices

import (
"encoding/json"
"strings"
"sync"

Expand Down Expand Up @@ -74,6 +75,11 @@ func (uc *UseCase) dtoToEntity(d *dto.Device) (*entity.Device, error) {

tags := strings.Join(d.Tags, ",")

deviceInfo, err := marshalDeviceInfo(d.DeviceInfo)
if err != nil {
return nil, ErrDeviceUseCase.Wrap("dtoToEntity", "marshalDeviceInfo", err)
}

d1 := &entity.Device{
ConnectionStatus: d.ConnectionStatus,
MPSInstance: d.MPSInstance,
Expand All @@ -87,15 +93,13 @@ func (uc *UseCase) dtoToEntity(d *dto.Device) (*entity.Device, error) {
LastConnected: d.LastConnected,
LastSeen: d.LastSeen,
LastDisconnected: d.LastDisconnected,
// DeviceInfo: d.DeviceInfo,
Username: d.Username,
Password: d.Password,
UseTLS: d.UseTLS,
AllowSelfSigned: d.AllowSelfSigned,
DeviceInfo: deviceInfo,
Username: d.Username,
Password: d.Password,
UseTLS: d.UseTLS,
AllowSelfSigned: d.AllowSelfSigned,
}

var err error

d1.Password, err = uc.safeRequirements.Encrypt(d1.Password)
if err != nil {
return nil, ErrDeviceUseCase.Wrap("dtoToEntity", "failed to encrypt password", err)
Expand Down Expand Up @@ -133,8 +137,7 @@ func (uc *UseCase) dtoToEntity(d *dto.Device) (*entity.Device, error) {
}

// Keys are lowercased to match encoding/json's case-insensitive unmarshal.
// guid and tenantId identify the record; deviceInfo doesn't round-trip through
// dtoToEntity/entityToDTO — all three are intentionally omitted.
// guid and tenantId identify the record and are intentionally omitted.
var deviceFieldSetters = map[string]func(dst, src *dto.Device){
"connectionstatus": func(dst, src *dto.Device) { dst.ConnectionStatus = src.ConnectionStatus },
"mpsinstance": func(dst, src *dto.Device) { dst.MPSInstance = src.MPSInstance },
Expand All @@ -153,6 +156,7 @@ var deviceFieldSetters = map[string]func(dst, src *dto.Device){
"usetls": func(dst, src *dto.Device) { dst.UseTLS = src.UseTLS },
"allowselfsigned": func(dst, src *dto.Device) { dst.AllowSelfSigned = src.AllowSelfSigned },
"certhash": func(dst, src *dto.Device) { dst.CertHash = src.CertHash },
"deviceinfo": func(dst, src *dto.Device) { dst.DeviceInfo = src.DeviceInfo },
}

func mergeDeviceFields(dst, src *dto.Device, fields map[string]bool) {
Expand All @@ -164,13 +168,18 @@ func mergeDeviceFields(dst, src *dto.Device, fields map[string]bool) {
}

// convert entity.Device to dto.Device.
func (uc *UseCase) entityToDTO(d *entity.Device) *dto.Device {
func (uc *UseCase) entityToDTO(d *entity.Device) (*dto.Device, error) {
// convert comma separated string to []string
var tags []string
if d.Tags != "" {
tags = strings.Split(d.Tags, ",")
}

deviceInfo, err := unmarshalDeviceInfo(d.DeviceInfo, d.GUID)
if err != nil {
return nil, err
}

d1 := &dto.Device{
ConnectionStatus: d.ConnectionStatus,
MPSInstance: d.MPSInstance,
Expand All @@ -184,11 +193,10 @@ func (uc *UseCase) entityToDTO(d *entity.Device) *dto.Device {
LastConnected: d.LastConnected,
LastSeen: d.LastSeen,
LastDisconnected: d.LastDisconnected,
// DeviceInfo: d.DeviceInfo,
Username: d.Username,
// Password: d.Password,
UseTLS: d.UseTLS,
AllowSelfSigned: d.AllowSelfSigned,
DeviceInfo: deviceInfo,
Username: d.Username,
UseTLS: d.UseTLS,
AllowSelfSigned: d.AllowSelfSigned,
}

if d.CertHash != nil {
Expand All @@ -203,5 +211,31 @@ func (uc *UseCase) entityToDTO(d *entity.Device) *dto.Device {
d1.MEBXPassword = *d.MEBXPassword
}

return d1
return d1, nil
}

func marshalDeviceInfo(info *dto.DeviceInfo) (string, error) {
if info == nil {
return "", nil
}

b, err := json.Marshal(info)
if err != nil {
return "", err
}

return string(b), nil
}

func unmarshalDeviceInfo(raw, guid string) (*dto.DeviceInfo, error) {
if raw == "" {
return nil, nil
}

var info dto.DeviceInfo
if err := json.Unmarshal([]byte(raw), &info); err != nil {
return nil, ErrDeviceUseCase.Wrap("unmarshalDeviceInfo", "failed to unmarshal deviceInfo for device "+guid, err)
}

return &info, nil
}
Loading
Loading