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 @@ -5,6 +5,7 @@ import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import { IAppStoreApp, ISoftwarePackage } from "interfaces/software";

import { NotificationContext } from "context/notification";
import { getErrorReason } from "interfaces/errors";
import softwareAPI from "services/entities/software";

import Modal from "components/Modal";
Expand Down Expand Up @@ -529,7 +530,8 @@ const EditIconModal = ({
setIconUploadedAt(new Date().toISOString());
onExitEditIconModal();
} catch (e) {
renderFlash("error", DEFAULT_ERROR_MESSAGE);
const errorMessage = getErrorReason(e) || DEFAULT_ERROR_MESSAGE;
renderFlash("error", errorMessage);
} finally {
setIsUpdatingIcon(false);
}
Expand Down
41 changes: 41 additions & 0 deletions server/service/integration_enterprise_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12226,6 +12226,47 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareTitleIcons() {
require.Equal(t, iconUrl, *details.SoftwareIconURL)
require.Equal(t, titleID, details.SoftwareTitleID)

// PUT: Icon too large
// Create a fake large file (101KB of data)
largeData := make([]byte, 101*1024)
reader := bytes.NewReader(largeData)
iconFile, err := fleet.NewTempFileReader(reader, func() string { return t.TempDir() })
require.NoError(t, err)

var bufInvalid bytes.Buffer
writer = multipart.NewWriter(&bufInvalid)
fileWriter, err = writer.CreateFormFile("icon", "icon.png")
require.NoError(t, err)
_, err = io.Copy(fileWriter, iconFile)
require.NoError(t, err)
err = writer.Close()
require.NoError(t, err)
headers = map[string]string{
"Content-Type": writer.FormDataContentType(),
"Authorization": fmt.Sprintf("Bearer %s", s.token),
}
resp = s.DoRawWithHeaders(
"PUT",
fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon?team_id=%d", titleID, tm.ID),
bufInvalid.Bytes(),
http.StatusBadRequest,
headers,
)
var errorResp struct {
Message string `json:"message"`
Errors []struct {
Name string `json:"name"`
Reason string `json:"reason"`
} `json:"errors"`
}
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
err = json.Unmarshal(body, &errorResp)
require.NoError(t, err)
assert.Equal(t, "Bad request", errorResp.Message)
require.Len(t, errorResp.Errors, 1)
assert.Contains(t, errorResp.Errors[0].Reason, "icon must be less than 100KB")

// PUT: gitops workflow, passing in sha256 & filename
var storedIcons []fleet.SoftwareTitleIcon
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
Expand Down
33 changes: 27 additions & 6 deletions server/service/software_title_icons.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,19 @@ func (putSoftwareTitleIconRequest) DecodeRequest(ctx context.Context, r *http.Re
}
}

// Validate the file if one is provided
if decoded.File != nil {
file, err := decoded.File.Open()
if err != nil {
return nil, &fleet.BadRequestError{Message: "failed to open file"}
}
defer file.Close()

if err := iconValidator(file); err != nil {
return nil, err
}
}

return &decoded, nil
}

Expand All @@ -189,12 +202,6 @@ func putSoftwareTitleIconEndpoint(ctx context.Context, request interface{}, svc
}
defer file.Close()

// ensure icon is png and fits sizing restrictions
err = iconValidator(file)
if err != nil {
return putSoftwareTitleIconResponse{Err: err}, nil
}

tfr, err := fleet.NewTempFileReader(file, nil)
if err != nil {
return putSoftwareTitleIconResponse{Err: err}, nil
Expand Down Expand Up @@ -228,6 +235,20 @@ func (svc *Service) UploadSoftwareTitleIcon(ctx context.Context, payload *fleet.
}

func iconValidator(file multipart.File) error {
// Check file size first
fileSize, err := file.Seek(0, 2) // Seek to end to get size
if err != nil {
return &fleet.BadRequestError{Message: "failed to read file size"}
}
if _, err := file.Seek(0, 0); err != nil { // Reset to beginning
return &fleet.BadRequestError{Message: "failed to rewind file"}
}

maxSize := int64(100 * 1024) // 100KB
if fileSize > maxSize {
return &fleet.BadRequestError{Message: "icon must be less than 100KB"}
}

config, format, err := image.DecodeConfig(file)
if err != nil || format != "png" {
return &fleet.BadRequestError{Message: "icon must be a PNG image"}
Expand Down
Loading