Skip to content

SAAD: Asset CRUD API - #49011

Merged
MagnusHJensen merged 12 commits into
mainfrom
48568-asset-api
Jul 9, 2026
Merged

SAAD: Asset CRUD API#49011
MagnusHJensen merged 12 commits into
mainfrom
48568-asset-api

Conversation

@MagnusHJensen

@MagnusHJensen MagnusHJensen commented Jul 9, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #48568 partly

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information. (Will add in followup)

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

  • Timeouts are implemented and retries are limited to avoid infinite loops

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

Summary by CodeRabbit

  • New Features
    • Added Apple DDM asset management endpoints: list, get, download (raw JSON), create, and delete.
    • Implemented datastore-backed Apple DDM asset CRUD with team-scoped and global access, plus configurable upload size limits.
    • Added strict asset JSON validation (including required fields, URI checks, and secret expansion rules).
  • Bug Fixes
    • Improved authorization handling by returning not-found responses for out-of-scope read/download/delete to avoid asset discovery.
    • Added clearer conflict and linked-profile error mapping for create/delete failures.
  • Tests
    • Added comprehensive authorization and validation test coverage for Apple DDM assets and policy behavior.

Copilot AI review requested due to automatic review settings July 9, 2026 07:34
@MagnusHJensen
MagnusHJensen requested review from a team and rachaelshaw as code owners July 9, 2026 07:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the first slice of the Apple DDM “asset” CRUD API to Fleet, wiring up new REST routes, authorization policy, service/datastore interfaces, MySQL datastore implementations, and initial test coverage. This aligns with issue #48568’s “Storage + REST API” portion (the PR is explicitly marked as partial).

Changes:

  • Register new /api/v1/fleet/assets endpoints (list/get/download via alt=media/create/delete) and add OSS stubs returning ErrMissingLicense.
  • Introduce new Fleet service/datastore interfaces + MySQL CRUD implementations and supporting Fleet types for DDM assets.
  • Add authz policy + tests, plus datastore and EE service tests for the new asset operations.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
server/service/handler.go Registers new asset CRUD routes under the Apple MDM middleware.
server/service/apple_mdm.go Adds request/response structs and endpoint handlers; OSS service methods stubbed for premium gating.
server/mock/service/service_mock.go Extends service mock with the new DDM asset methods.
server/mock/datastore_mock.go Extends datastore mock with the new DDM asset methods.
server/fleet/service.go Adds DDM asset methods to the Fleet Service interface.
server/fleet/request.go Introduces MaxMDMAssetSize request size constant.
server/fleet/datastore.go Adds DDM asset CRUD methods to the datastore interface.
server/fleet/apple_mdm.go Introduces DDMAsset* structs and authz wrapper type for DDM assets.
server/datastore/mysql/apple_mdm.go Implements MySQL CRUD for DDM assets.
server/datastore/mysql/apple_mdm_test.go Adds unit tests for the new MySQL DDM asset CRUD methods.
server/authz/policy.rego Adds read/write policy rules for ddm_asset objects.
server/authz/policy_test.go Adds test coverage for the new ddm_asset authorization rules.
server/api_endpoints/api_endpoints.yml Documents the new public API endpoints.
ee/server/service/apple_mdm.go Implements the real (EE) service logic for listing/getting/downloading/creating/deleting assets.
ee/server/service/apple_mdm_test.go Adds initial EE service-layer tests for list/get/download/delete authorization behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/datastore/mysql/apple_mdm.go
Comment thread server/service/handler.go Outdated
Comment thread server/service/apple_mdm.go
Comment thread server/service/apple_mdm.go
Comment thread ee/server/service/apple_mdm.go
Comment thread server/api_endpoints/api_endpoints.yml Outdated
Comment thread server/api_endpoints/api_endpoints.yml Outdated
Comment thread ee/server/service/apple_mdm.go
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The head commit changed during the review from a61c4a5 to a53207a.

Walkthrough

This change adds Apple DDM asset support across authorization, datastore CRUD, service interfaces, HTTP handlers, mocks, and tests. It introduces DDM asset types and authz typing, adds ddm_asset policy rules, implements MySQL-backed list/get/download/create/delete operations, wires new /api/v1/fleet/assets routes, adds premium service validation and scoped authorization, and updates mocks and tests for the new asset workflows.

Possibly related PRs

  • fleetdm/fleet#48866: Introduces the underlying Apple DDM asset and reference tables used by the new datastore CRUD and delete-constraint handling.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers asset CRUD endpoints and authz, but it does not implement reference tracking, DDM sync, asset propagation, or delete guards from #48568. Add FindAssetReferences, asset-reference persistence and validation, DDM sync/token propagation, delete blocking, and status-report handling.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main change: adding asset CRUD API support.
Description check ✅ Passed The description covers the issue link, checklist, and testing, and the omitted sections are non-critical or marked N/A.
Out of Scope Changes check ✅ Passed The changes stay focused on Apple DDM asset CRUD, authz, validation, tests, and endpoint wiring.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 48568-asset-api

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
server/datastore/mysql/apple_mdm_test.go (1)

55-59: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Solid coverage — consider adding a regression test for the non-duplicate insert-error path.

Coverage for validation, duplicate-conflict, and FK-violation paths is thorough. One gap: there's no test forcing a non-duplicate DB error on CreateAppleDDMAsset's insert (e.g. via a bad column/type or a mocked writer failure), which is exactly the scenario affected by the error-swallowing bug flagged in server/datastore/mysql/apple_mdm.go. Adding one would lock in the fix once applied.

Also applies to: 13295-13495

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm_test.go` around lines 55 - 59, Add a
regression test for CreateAppleDDMAsset that forces a non-duplicate insert
failure on the initial DB insert path, not a validation, duplicate-key, or FK
error. Use the existing apple_mdm_test.go test table and the CreateAppleDDMAsset
flow to simulate a generic writer/DB failure (for example by mocking the
inserter or using an invalid write scenario), then assert the error is returned
instead of being swallowed. This will protect the fix in apple_mdm.go and cover
the missing non-duplicate error path.
ee/server/service/apple_mdm_test.go (1)

1-348: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing test coverage for CreateAppleDDMAsset/validateAppleDDMAsset.

This file tests List/Get/Download/Delete thoroughly but has no test for CreateAppleDDMAsset, which is the most complex path (authz, JSON parsing, secret expansion, and duplicate-name/identifier conflict mapping). Given the data-flow concern raised in apple_mdm.go around secret expansion at create time, a test asserting the persisted/created payload reflects the expected (expanded) data would materially reduce risk here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/server/service/apple_mdm_test.go` around lines 1 - 348, Add test coverage
for the CreateAppleDDMAsset path, since apple_mdm_test.go currently only
exercises List/Get/Download/Delete. Create a focused test around
CreateAppleDDMAsset that verifies authz handling plus validateAppleDDMAsset
behavior, including JSON parsing, secret expansion, and
duplicate-name/identifier conflict mapping; use the existing service method
names and mock.Store hooks to assert the created payload is the
expanded/persisted version.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/datastore/mysql/apple_mdm.go`:
- Around line 7852-7897: The CreateAppleDDMAsset insert path is swallowing
non-duplicate errors and then returning assetUUID as if the row was created
successfully. Update the error handling around ds.writer(ctx).ExecContext in
CreateAppleDDMAsset so any non-nil err that is not handled as a duplicate also
returns a wrapped error instead of falling through. Keep the existing
duplicate-key mapping logic for IsDuplicate(err), but add a fallback return in
the err block so callers never receive a fake success.

In `@server/service/apple_mdm.go`:
- Around line 3786-3796: The multipart form parsing in the fleet_id handling
path currently accepts negative values and then converts them to uint, which can
silently wrap to a huge TeamID. Update the fleet_id validation in the
apple_mdm.go multipart decode logic to reject any parsed value less than zero
before assigning to decoded.TeamID, and return the same BadRequestError used for
other invalid fleet_id inputs. Keep the fix localized to the fleet_id parsing
block that uses strconv.Atoi and decoded.TeamID.

---

Nitpick comments:
In `@ee/server/service/apple_mdm_test.go`:
- Around line 1-348: Add test coverage for the CreateAppleDDMAsset path, since
apple_mdm_test.go currently only exercises List/Get/Download/Delete. Create a
focused test around CreateAppleDDMAsset that verifies authz handling plus
validateAppleDDMAsset behavior, including JSON parsing, secret expansion, and
duplicate-name/identifier conflict mapping; use the existing service method
names and mock.Store hooks to assert the created payload is the
expanded/persisted version.

In `@server/datastore/mysql/apple_mdm_test.go`:
- Around line 55-59: Add a regression test for CreateAppleDDMAsset that forces a
non-duplicate insert failure on the initial DB insert path, not a validation,
duplicate-key, or FK error. Use the existing apple_mdm_test.go test table and
the CreateAppleDDMAsset flow to simulate a generic writer/DB failure (for
example by mocking the inserter or using an invalid write scenario), then assert
the error is returned instead of being swallowed. This will protect the fix in
apple_mdm.go and cover the missing non-duplicate error path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bc85185c-89bd-41d5-8b25-b910d115b91a

📥 Commits

Reviewing files that changed from the base of the PR and between e109409 and e308d38.

📒 Files selected for processing (15)
  • ee/server/service/apple_mdm.go
  • ee/server/service/apple_mdm_test.go
  • server/api_endpoints/api_endpoints.yml
  • server/authz/policy.rego
  • server/authz/policy_test.go
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/apple_mdm_test.go
  • server/fleet/apple_mdm.go
  • server/fleet/datastore.go
  • server/fleet/request.go
  • server/fleet/service.go
  • server/mock/datastore_mock.go
  • server/mock/service/service_mock.go
  • server/service/apple_mdm.go
  • server/service/handler.go

Comment thread server/datastore/mysql/apple_mdm.go
Comment thread server/service/apple_mdm.go
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 123 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.09%. Comparing base (ace8cf0) to head (a53207a).
⚠️ Report is 29 commits behind head on main.

Files with missing lines Patch % Lines
server/service/apple_mdm.go 0.00% 89 Missing ⚠️
ee/server/service/apple_mdm.go 68.42% 16 Missing and 8 partials ⚠️
server/datastore/mysql/apple_mdm.go 86.66% 7 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #49011      +/-   ##
==========================================
+ Coverage   68.08%   68.09%   +0.01%     
==========================================
  Files        3709     3709              
  Lines      235098   235603     +505     
  Branches    12353    12353              
==========================================
+ Hits       160073   160441     +368     
- Misses      60637    60757     +120     
- Partials    14388    14405      +17     
Flag Coverage Δ
backend 69.70% <50.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
ee/server/service/apple_mdm_test.go (2)

351-503: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test coverage for AlreadyExistsErrorConflictError translation.

The service implementation translates AlreadyExistsError into ConflictError for both "asset_name" and "asset_identifier" cases (ee/server/service/apple_mdm.go:150-157), but no subtest exercises this path. This is important business logic that should be tested to prevent regressions.

🧪 Suggested conflict translation subtests
 	t.Run("Expanded payload with authentication key is rejected", func(t *testing.T) {
 		// ... existing test ...
 	})
+
+	t.Run("Duplicate name returns conflict error", func(t *testing.T) {
+		defer reset()
+		ds.CreateAppleDDMAssetFunc = func(ctx context.Context, name, identifier string, data []byte, teamID *uint) (string, error) {
+			return "", fleet.AlreadyExistsError{Message: "asset_name: asset already exists for team"}
+		}
+		_, err := svc.CreateAppleDDMAsset(ctx, nil, "asset", validData)
+		require.Error(t, err)
+		var conflictErr *fleet.ConflictError
+		require.ErrorAs(t, err, &conflictErr)
+		require.Contains(t, conflictErr.Error(), "already exists for this team")
+	})
+
+	t.Run("Duplicate identifier returns conflict error", func(t *testing.T) {
+		defer reset()
+		ds.CreateAppleDDMAssetFunc = func(ctx context.Context, name, identifier string, data []byte, teamID *uint) (string, error) {
+			return "", fleet.AlreadyExistsError{Message: "asset_identifier: identifier already exists for team"}
+		}
+		_, err := svc.CreateAppleDDMAsset(ctx, nil, "asset", validData)
+		require.Error(t, err)
+		var conflictErr *fleet.ConflictError
+		require.ErrorAs(t, err, &conflictErr)
+		require.Contains(t, conflictErr.Error(), "already exists for this team")
+	})
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/server/service/apple_mdm_test.go` around lines 351 - 503, Add subtest
coverage in TestCreateAppleDDMAsset for the AlreadyExistsError to ConflictError
translation path. Update the mock Store.CreateAppleDDMAssetFunc to return an
AlreadyExistsError for both the asset_name and asset_identifier cases, then
assert CreateAppleDDMAsset returns a ConflictError in each scenario. Use the
existing svc.CreateAppleDDMAsset test harness and the mock.Store symbols to keep
the new cases aligned with the service’s conflict handling logic.

421-451: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert ExpandEmbeddedSecretsAndUpdatedAtFuncInvoked is false in field-validation subtests for consistency.

The "Malformed JSON" and secret-in-type/identifier subtests assert ds.ExpandEmbeddedSecretsAndUpdatedAtFuncInvoked is false, but the empty-identifier, invalid-type, empty-DataURL, and invalid-DataURL subtests only assert ds.CreateAppleDDMAssetFuncInvoked is false. Adding the expansion assertion here too would confirm that field validation gates expansion consistently and protect against future reordering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/server/service/apple_mdm_test.go` around lines 421 - 451, The
field-validation subtests in CreateAppleDDMAssetTest only verify that
ds.CreateAppleDDMAssetFuncInvoked stays false, but they should also assert
ds.ExpandEmbeddedSecretsAndUpdatedAtFuncInvoked is false for consistency with
the other validation cases. Update the empty-identifier, invalid-asset-type,
empty-DataURL, and invalid-DataURL subtests in apple_mdm_test.go to include the
same expansion-not-invoked assertion used in the Malformed JSON and
secret-related cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@ee/server/service/apple_mdm_test.go`:
- Around line 351-503: Add subtest coverage in TestCreateAppleDDMAsset for the
AlreadyExistsError to ConflictError translation path. Update the mock
Store.CreateAppleDDMAssetFunc to return an AlreadyExistsError for both the
asset_name and asset_identifier cases, then assert CreateAppleDDMAsset returns a
ConflictError in each scenario. Use the existing svc.CreateAppleDDMAsset test
harness and the mock.Store symbols to keep the new cases aligned with the
service’s conflict handling logic.
- Around line 421-451: The field-validation subtests in CreateAppleDDMAssetTest
only verify that ds.CreateAppleDDMAssetFuncInvoked stays false, but they should
also assert ds.ExpandEmbeddedSecretsAndUpdatedAtFuncInvoked is false for
consistency with the other validation cases. Update the empty-identifier,
invalid-asset-type, empty-DataURL, and invalid-DataURL subtests in
apple_mdm_test.go to include the same expansion-not-invoked assertion used in
the Malformed JSON and secret-related cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ffdf76e4-3460-4142-b82f-61caa7e1ddb1

📥 Commits

Reviewing files that changed from the base of the PR and between e308d38 and a3652e9.

📒 Files selected for processing (1)
  • ee/server/service/apple_mdm_test.go

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
server/datastore/mysql/apple_mdm_ddm_test.go (1)

444-470: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider asserting returned asset fields in the "returns asset" subtest.

The "returns asset for existing asset" subtest only checks require.NotNil(t, asset) but doesn't verify any field values (e.g., asset.Name, asset.Identifier). The download variant at lines 481-492 does assert field values. Adding basic field assertions here would strengthen coverage and catch silent column-mapping regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm_ddm_test.go` around lines 444 - 470, The
"returns asset for existing asset" subtest in testGetAppleDDMAsset only checks
that GetAppleDDMAsset returns a non-nil result, so extend it to assert the
returned AppleDDMAsset fields match the values passed to CreateAppleDDMAsset,
such as Name and Identifier. Use the existing GetAppleDDMAsset and
CreateAppleDDMAsset calls in that subtest as the anchor, and mirror the
field-level coverage already used in the download variant to catch mapping
regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@server/datastore/mysql/apple_mdm_ddm_test.go`:
- Around line 444-470: The "returns asset for existing asset" subtest in
testGetAppleDDMAsset only checks that GetAppleDDMAsset returns a non-nil result,
so extend it to assert the returned AppleDDMAsset fields match the values passed
to CreateAppleDDMAsset, such as Name and Identifier. Use the existing
GetAppleDDMAsset and CreateAppleDDMAsset calls in that subtest as the anchor,
and mirror the field-level coverage already used in the download variant to
catch mapping regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af128f0f-60fb-4860-96d3-80a35be6387c

📥 Commits

Reviewing files that changed from the base of the PR and between a3652e9 and c529276.

📒 Files selected for processing (6)
  • ee/server/service/apple_mdm.go
  • server/api_endpoints/api_endpoints.yml
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/apple_mdm_ddm_test.go
  • server/service/apple_mdm.go
  • server/service/handler.go
💤 Files with no reviewable changes (1)
  • server/datastore/mysql/apple_mdm.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • server/api_endpoints/api_endpoints.yml
  • server/service/handler.go
  • server/service/apple_mdm.go
  • ee/server/service/apple_mdm.go

@MagnusHJensen
MagnusHJensen removed the request for review from rachaelshaw July 9, 2026 10:55

@JordanMontgomery JordanMontgomery left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested this yet but overall code looks good and I am just about to test with the frontend so no concerns

@MagnusHJensen
MagnusHJensen merged commit a15d58e into main Jul 9, 2026
44 checks passed
@MagnusHJensen
MagnusHJensen deleted the 48568-asset-api branch July 9, 2026 16:34
@coderabbitai coderabbitai Bot mentioned this pull request Jul 9, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SAAD Backend: Asset API handlers + DDM Sync

3 participants