Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -1419,9 +1419,55 @@
"profile",
"{{deviceId}}"
]
},
"description": "Adds a wireless profile to an AMT device.\n\nThe test script expects 404 because the integration test run has no device.\n\nOther responses:\n- 204 No Content: the profile was added.\n- 409 Conflict: a profile with the same name or priority already exists, or the device already stores the maximum of 8 wireless profiles. To reproduce the limit case, add 8 profiles with different names and priorities to the device first, then send a 9th. AMT \"User Settings\" entries do not count toward the limit. See the saved example \"409 Conflict - maximum of 8 wireless profiles reached\"."
},
"response": [
{
"name": "409 Conflict - maximum of 8 wireless profiles reached",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"profileName\": \"Profile9\",\r\n \"ssid\": \"Profile9SSID\",\r\n \"priority\": 9,\r\n \"authenticationMethod\": \"WPA2PSK\",\r\n \"encryptionMethod\": \"CCMP\",\r\n \"password\": \"password123\"\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{protocol}}://{{host}}/api/v1/amt/networkSettings/wireless/profile/123e4567-e89b-12d3-a456-426614174000",
"protocol": "{{protocol}}",
"host": [
"{{host}}"
],
"path": [
"api",
"v1",
"amt",
"networkSettings",
"wireless",
"profile",
"123e4567-e89b-12d3-a456-426614174000"
]
},
"description": "Prerequisite: the device already stores 8 wireless profiles (profiles 1 to 8, with priorities other than 9). This request adds a 9th."
},
"status": "Conflict",
"code": 409,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
}
],
"cookie": [],
"body": "{\n \"error\": \"Add Wireless Profile failed for guid: 123e4567-e89b-12d3-a456-426614174000. - device already has the maximum of 8 wireless profiles; delete a profile before adding a new one\"\n}"
}
},
"response": []
]
},
{
"name": "Add Duplicate Wireless Profile",
Expand Down
9 changes: 9 additions & 0 deletions internal/controller/httpapi/v1/wifiprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/device-management-toolkit/console/internal/controller/httpapi/middleware"
"github.com/device-management-toolkit/console/internal/entity/dto/v1"
"github.com/device-management-toolkit/console/internal/usecase/devices"
"github.com/device-management-toolkit/console/internal/usecase/devices/wsman"
"github.com/device-management-toolkit/console/pkg/consoleerrors"
)
Expand Down Expand Up @@ -54,6 +55,14 @@ func (r *deviceManagementRoutes) addWirelessProfile(c *gin.Context) {
return
}

if errors.Is(err, devices.ErrWirelessProfileLimitReached) {
c.JSON(http.StatusConflict, gin.H{
errorKey: "Add Wireless Profile failed for guid: " + guid + ". - " + err.Error(),
})
Comment thread
choonkeat1986 marked this conversation as resolved.
Resolved

return
}

ErrorResponse(c, err)

return
Expand Down
11 changes: 11 additions & 0 deletions internal/controller/httpapi/v1/wifiprofile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

dto "github.com/device-management-toolkit/console/internal/entity/dto/v1"
"github.com/device-management-toolkit/console/internal/mocks"
"github.com/device-management-toolkit/console/internal/usecase/devices"
"github.com/device-management-toolkit/console/internal/usecase/devices/wsman"
)

Expand Down Expand Up @@ -112,6 +113,16 @@ func TestWiFiProfileRoutes(t *testing.T) { //nolint:gocognit // table-driven HTT
},
expectedCode: http.StatusNotFound,
},
{
name: "add wireless profile - profile limit reached",
method: http.MethodPost,
url: "/api/v1/amt/networkSettings/wireless/profile/device-guid",
requestBody: request,
mock: func(feature *mocks.MockDeviceManagementFeature) {
feature.EXPECT().AddWirelessProfile(context.Background(), "device-guid", "", request).Return(devices.ErrWirelessProfileLimitReached)
},
expectedCode: http.StatusConflict,
},
{
name: "update wireless profile",
method: http.MethodPatch,
Expand Down
4 changes: 3 additions & 1 deletion internal/controller/openapi/devicemanagement.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ func (f *FuegoAdapter) registerNetworkRoutes() {
f.server, "/api/v1/amt/networkSettings/wireless/profile/{guid}", f.addWirelessProfile,
fuego.OptionTags("Device Management"),
fuego.OptionSummary("Create Wireless Profile"),
fuego.OptionDescription("Create a wireless profile on a device. "+wirelessProfileRequestRules),
fuego.OptionDescription("Create a wireless profile on a device. "+wirelessProfileRequestRules+". "+
"A device stores at most 8 wireless profiles; adding one more, or a profile whose name or priority "+
"already exists, is rejected with 409 Conflict"),
fuego.OptionPath("guid", "Device GUID"),
fuego.OptionDefaultStatusCode(http.StatusNoContent),
protectedRouteOptions(),
Expand Down
28 changes: 28 additions & 0 deletions internal/usecase/devices/wifiprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ const (
resourceCIMWiFiEndpointSettings = "CIM_WiFiEndpointSettings"
resourceCIMIEEE8021xSettings = "CIM_IEEE8021xSettings"
selectorNameInstanceID = "InstanceID"
// MaxWirelessProfiles is the number of user-defined wireless profiles AMT can store.
MaxWirelessProfiles = 8
)

var (
errInvalidAuthenticationMethod = errors.New("invalid authentication method")
errInvalidEncryptionMethod = errors.New("invalid encryption method")

// ErrWirelessProfileLimitReached is returned when a device already stores
// MaxWirelessProfiles wireless profiles. The HTTP layer maps it to 409 Conflict.
ErrWirelessProfileLimitReached = errors.New("device already has the maximum of 8 wireless profiles; delete a profile before adding a new one")
)

type IEEE8021xCertHandles struct {
Expand Down Expand Up @@ -81,6 +87,12 @@ func (uc *UseCase) AddWirelessProfile(c context.Context, guid, tenantID string,
return wirelessProfilePriorityAlreadyExists(profile.Priority)
}

// Check the limit before any certificate is added, so a rejected request
// leaves nothing behind on the device.
if countWirelessProfiles(settings) >= MaxWirelessProfiles {
return ErrWirelessProfileLimitReached
}

preparedProfile, needsPauseBeforeApply, err := prepareWirelessProfileForApply(device, profile)
if err != nil {
return err
Expand Down Expand Up @@ -260,6 +272,22 @@ func findWirelessSettingByProfileName(settings []wifi.WiFiEndpointSettingsRespon
return wifi.WiFiEndpointSettingsResponse{}, false
}

// countWirelessProfiles counts the user-defined profiles, skipping the
// "User Settings" entries that AMT reports alongside them.
func countWirelessProfiles(settings []wifi.WiFiEndpointSettingsResponse) int {
count := 0

for i := range settings {
if settings[i].InstanceID == "" || isUserSettingsInstanceID(settings[i].InstanceID) {
continue
}

count++
}

return count
}

func findWirelessSettingByPriority(settings []wifi.WiFiEndpointSettingsResponse, priority int) (wifi.WiFiEndpointSettingsResponse, bool) {
for i := range settings {
setting := settings[i]
Expand Down
99 changes: 99 additions & 0 deletions internal/usecase/devices/wifiprofile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,105 @@ func TestAddWirelessProfile(t *testing.T) { //nolint:gocognit // table-driven co
}
}

// wirelessProfileSettings returns count user-defined profiles with priorities
// starting at 2, so they do not clash with a request that uses priority 1.
func wirelessProfileSettings(count int) []wifi.WiFiEndpointSettingsResponse {
settings := make([]wifi.WiFiEndpointSettingsResponse, 0, count)

for i := 0; i < count; i++ {
name := fmt.Sprintf("Profile%d", i)
settings = append(settings, wifi.WiFiEndpointSettingsResponse{
InstanceID: "Intel(r) AMT:WiFi Endpoint Settings " + name,
ElementName: name,
Priority: i + 2,
})
}

return settings
}

func TestAddWirelessProfileLimit(t *testing.T) {
t.Parallel()

device := &entity.Device{GUID: "device-guid-123"}
profile := config.WirelessProfile{
ProfileName: "Home",
SSID: "HomeSSID",
Priority: 1,
Password: "password",
AuthenticationMethod: "WPA2PSK",
EncryptionMethod: "CCMP",
}
userSettings := wifi.WiFiEndpointSettingsResponse{InstanceID: "Intel(r) AMT:WiFi Endpoint User Settings 1", ElementName: "User", Priority: 1}

tests := []struct {
name string
profile config.WirelessProfile
settings []wifi.WiFiEndpointSettingsResponse
expectAdd bool
wantErrIs error
}{
{
name: "one below the limit is added",
profile: profile,
settings: wirelessProfileSettings(devices.MaxWirelessProfiles - 1),
expectAdd: true,
},
{
name: "user settings entries do not count toward the limit",
profile: config.WirelessProfile{ProfileName: "Home", SSID: "HomeSSID", Priority: 20, Password: "password", AuthenticationMethod: "WPA2PSK", EncryptionMethod: "CCMP"},
settings: append(wirelessProfileSettings(devices.MaxWirelessProfiles-1), userSettings),
expectAdd: true,
},
{
name: "at the limit is rejected",
profile: profile,
settings: wirelessProfileSettings(devices.MaxWirelessProfiles),
wantErrIs: devices.ErrWirelessProfileLimitReached,
},
{
name: "at the limit is rejected before 802.1x certificates are added",
profile: config.WirelessProfile{
ProfileName: "Corp",
SSID: "CorpSSID",
Priority: 1,
AuthenticationMethod: "WPA2IEEE8021x",
EncryptionMethod: "CCMP",
IEEE8021x: &config.IEEE8021x{PrivateKey: "new-private"},
},
settings: wirelessProfileSettings(devices.MaxWirelessProfiles),
wantErrIs: devices.ErrWirelessProfileLimitReached,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

useCase, wsmanMock, management, repo := initWiFiProfileTest(t)

repo.EXPECT().GetByID(context.Background(), device.GUID, device.TenantID).Return(device, nil)
wsmanMock.EXPECT().SetupWsmanClient(gomock.Any(), gomock.Any(), false, true).Return(management, nil)
expectWiFiPortPresent(management)
management.EXPECT().GetWiFiSettings().Return(tc.settings, nil)

if tc.expectAdd {
management.EXPECT().AddWiFiSettings(expectedWiFiRequest(tc.profile), models.IEEE8021xSettings{}, testWiFiEndpoint, "", "").Return(wifiportconfiguration.Response{}, nil)
}

err := useCase.AddWirelessProfile(context.Background(), device.GUID, device.TenantID, tc.profile)
if tc.wantErrIs != nil {
require.ErrorIs(t, err, tc.wantErrIs)

return
}

require.NoError(t, err)
})
}
}

func TestDeleteWirelessProfile(t *testing.T) {
t.Parallel()

Expand Down
Loading