Add macOS DDM protocol endpoints for tokens, declaration-items, and declaration/.../... (#17679)

This commit is contained in:
Sarah Gillespie 2024-03-18 17:48:07 -05:00 committed by GitHub
parent 93f040f7da
commit 3d73174e90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 502 additions and 26 deletions

View File

@ -390,7 +390,7 @@ func (c *TestAppleMDMClient) TokenUpdate() error {
// The endpoint argument is used as the value for the `Endpoint` key in the request payload.
//
// For more details check https://developer.apple.com/documentation/devicemanagement/declarativemanagementrequest
func (c *TestAppleMDMClient) DeclarativeManagement(endpoint string) error {
func (c *TestAppleMDMClient) DeclarativeManagement(endpoint string) (*http.Response, error) {
payload := map[string]any{
"MessageType": "DeclarativeManagement",
"UDID": c.UUID,
@ -398,8 +398,8 @@ func (c *TestAppleMDMClient) DeclarativeManagement(endpoint string) error {
"EnrollmentID": "testenrollmentid-" + c.UUID,
"Endpoint": endpoint,
}
_, err := c.request("application/x-apple-aspen-mdm-checkin", payload)
return err
r, err := c.request("application/x-apple-aspen-mdm-checkin", payload)
return r, err
}
// Checkout sends the CheckOut message to the MDM server.

View File

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"strings"
@ -3081,6 +3082,64 @@ WHERE h.uuid = ?
return nil
}
func (ds *Datastore) MDMAppleDDMSynchronizationTokens(ctx context.Context, teamID uint) (*fleet.MDMAppleDDMSyncTokens, error) {
const stmt = `
SELECT
md5_checksum,
latest_created_timestamp
FROM
team_declaration_checksum_view
WHERE
team_id = ?`
var res fleet.MDMAppleDDMSyncTokens
if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, teamID); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get DDM checksum by team id")
}
return &res, nil
}
func (ds *Datastore) MDMAppleDDMDeclarationItems(ctx context.Context, teamID uint) ([]fleet.MDMAppleDDMDeclarationItem, error) {
// TODO: Confirm whether we can use JSON functions in the query (if 5.7 officially unsupported
// by Fleet)
const stmt = `
SELECT
mad.md5_checksum as server_token,
identifier,
declaration_type,
tv.md5_checksum as declarations_token
FROM
mdm_apple_declarations mad
JOIN team_declaration_checksum_view tv ON mad.team_id = tv.team_id
WHERE
mad.team_id = ?`
var res []fleet.MDMAppleDDMDeclarationItem
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &res, stmt, teamID); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get DDM checksum by team id")
}
return res, nil
}
func (ds *Datastore) MDMAppleDDMDeclarationPayload(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, teamID uint) (json.RawMessage, error) {
const stmt = `
SELECT
declaration
FROM
mdm_apple_declarations
WHERE
team_id = ? AND identifier = ? AND declaration_type = ?`
var res json.RawMessage
if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, teamID, identifier, declarationType); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get ddm declaration")
}
return res, nil
}
func (ds *Datastore) MDMAppleRecordDeclarativeCheckIn(ctx context.Context, hostUUID string, result []byte) error {
err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
res, err := tx.ExecContext(
@ -3150,6 +3209,9 @@ UPDATE
return ctxerr.Wrap(ctx, err, "updating nano_command_results")
})
if err != nil {
return ctxerr.Wrap(ctx, err, "saving declarative management response")
}
return ctxerr.Wrap(ctx, err, "saving declarative management response")
return nil
}

View File

@ -609,3 +609,67 @@ type MDMAppleHostDeclaration struct {
// either by the MDM protocol or the Fleet server.
Detail string `db:"detail" json:"detail"`
}
// MDMAppleDDMTokensResponse is the response from the DDM tokens endpoint.
//
// https://developer.apple.com/documentation/devicemanagement/tokensresponse
type MDMAppleDDMTokensResponse struct {
SyncTokens MDMAppleDDMSyncTokens
}
// MDMAppleDDMSyncTokens is dictionary describes the state of declarations on the server.
//
// https://developer.apple.com/documentation/devicemanagement/synchronizationtokens
type MDMAppleDDMSyncTokens struct {
DeclarationsToken string `db:"md5_checksum"`
Timestamp time.Time `db:"latest_created_timestamp"`
}
// MDMAppleDDMDeclarationItemsResponse is the response from the DDM declaration items endpoint.
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse
type MDMAppleDDMDeclarationItemsResponse struct {
Declarations MDMAppleDDMManifestItems
DeclarationsToken string
}
// MDMAppleDDMManifestItems is a dictionary that contains the lists of declarations available on the
// server.
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse/manifestdeclarationitems
type MDMAppleDDMManifestItems struct {
Activations []MDMAppleDDMManifest
Assets []MDMAppleDDMManifest
Configurations []MDMAppleDDMManifest
Management []MDMAppleDDMManifest
}
// MDMAppleDDMManifest is a dictionary that describes a declaration.
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse/manifestdeclarationitems
type MDMAppleDDMManifest struct {
Identifier string
ServerToken string
}
// MDMAppleDDMDeclarationItem represents a declaration item in the datastore. It is used to
// construct the DDM `declaration-items` endpoint response.
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse
type MDMAppleDDMDeclarationItem struct {
Identifier string `db:"identifier"`
DeclarationType string `db:"declaration_type"`
DeclarationsToken string `db:"declarations_token"`
ServerToken string `db:"server_token"`
}
// MDMAppleDDMDeclarationResponse represents a declaration in the datastore. It is used for the DDM
// `declaration/.../...` enpoint response.
//
// https://developer.apple.com/documentation/devicemanagement/declarationresponse
type MDMAppleDDMDeclarationResponse struct {
Identifier string `db:"identifier"`
Type string `db:"type"`
Payload json.RawMessage `db:"payload"`
ServerToken string `db:"server_token"`
}

View File

@ -1157,6 +1157,14 @@ type Datastore interface {
// serials.
UpdateDEPAssignProfileRetryPending(ctx context.Context, jobID uint, serials []string) error
// MDMAppleDDMSynchronizationTokens returns the token used to synchronize declarations for the
// specified team or no team.
MDMAppleDDMSynchronizationTokens(ctx context.Context, teamID uint) (*MDMAppleDDMSyncTokens, error)
// MDMAppleDDMDeclarationItems returns the declaration items for the specified team or no team.
MDMAppleDDMDeclarationItems(ctx context.Context, teamID uint) ([]MDMAppleDDMDeclarationItem, error)
// MDMAppleDDMDeclarationPayload returns the declaration payload for the specified identifier and team.
MDMAppleDDMDeclarationPayload(ctx context.Context, declarationType MDMAppleDeclarationType, identifier string, teamID uint) (json.RawMessage, error)
///////////////////////////////////////////////////////////////////////////////
// Microsoft MDM

View File

@ -758,6 +758,12 @@ type GetDEPAssignProfileExpiredCooldownsFunc func(ctx context.Context) (map[uint
type UpdateDEPAssignProfileRetryPendingFunc func(ctx context.Context, jobID uint, serials []string) error
type MDMAppleDDMSynchronizationTokensFunc func(ctx context.Context, teamID uint) (*fleet.MDMAppleDDMSyncTokens, error)
type MDMAppleDDMDeclarationItemsFunc func(ctx context.Context, teamID uint) ([]fleet.MDMAppleDDMDeclarationItem, error)
type MDMAppleDDMDeclarationPayloadFunc func(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, teamID uint) (json.RawMessage, error)
type WSTEPStoreCertificateFunc func(ctx context.Context, name string, crt *x509.Certificate) error
type WSTEPNewSerialFunc func(ctx context.Context) (*big.Int, error)
@ -1971,6 +1977,15 @@ type DataStore struct {
UpdateDEPAssignProfileRetryPendingFunc UpdateDEPAssignProfileRetryPendingFunc
UpdateDEPAssignProfileRetryPendingFuncInvoked bool
MDMAppleDDMSynchronizationTokensFunc MDMAppleDDMSynchronizationTokensFunc
MDMAppleDDMSynchronizationTokensFuncInvoked bool
MDMAppleDDMDeclarationItemsFunc MDMAppleDDMDeclarationItemsFunc
MDMAppleDDMDeclarationItemsFuncInvoked bool
MDMAppleDDMDeclarationPayloadFunc MDMAppleDDMDeclarationPayloadFunc
MDMAppleDDMDeclarationPayloadFuncInvoked bool
WSTEPStoreCertificateFunc WSTEPStoreCertificateFunc
WSTEPStoreCertificateFuncInvoked bool
@ -4717,6 +4732,27 @@ func (s *DataStore) UpdateDEPAssignProfileRetryPending(ctx context.Context, jobI
return s.UpdateDEPAssignProfileRetryPendingFunc(ctx, jobID, serials)
}
func (s *DataStore) MDMAppleDDMSynchronizationTokens(ctx context.Context, teamID uint) (*fleet.MDMAppleDDMSyncTokens, error) {
s.mu.Lock()
s.MDMAppleDDMSynchronizationTokensFuncInvoked = true
s.mu.Unlock()
return s.MDMAppleDDMSynchronizationTokensFunc(ctx, teamID)
}
func (s *DataStore) MDMAppleDDMDeclarationItems(ctx context.Context, teamID uint) ([]fleet.MDMAppleDDMDeclarationItem, error) {
s.mu.Lock()
s.MDMAppleDDMDeclarationItemsFuncInvoked = true
s.mu.Unlock()
return s.MDMAppleDDMDeclarationItemsFunc(ctx, teamID)
}
func (s *DataStore) MDMAppleDDMDeclarationPayload(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, teamID uint) (json.RawMessage, error) {
s.mu.Lock()
s.MDMAppleDDMDeclarationPayloadFuncInvoked = true
s.mu.Unlock()
return s.MDMAppleDDMDeclarationPayloadFunc(ctx, declarationType, identifier, teamID)
}
func (s *DataStore) WSTEPStoreCertificate(ctx context.Context, name string, crt *x509.Certificate) error {
s.mu.Lock()
s.WSTEPStoreCertificateFuncInvoked = true

View File

@ -2974,27 +2974,40 @@ func (svc *MDMAppleDDMService) DeclarativeManagement(r *mdm.Request, dm *mdm.Dec
}
level.Debug(svc.logger).Log("msg", "ddm request received", "endpoint", dm.Endpoint)
if dm.UDID == "" {
return nil, ctxerr.New(r.Context, "missing device id")
}
h, err := svc.ds.HostLiteByIdentifier(r.Context, dm.UDID)
if err != nil {
return nil, ctxerr.Wrap(r.Context, err, "getting host by identifier")
}
var tid uint
if h.TeamID != nil {
tid = *h.TeamID
}
switch {
case dm.Endpoint == "tokens":
level.Debug(svc.logger).Log("msg", "received tokens request")
// TODO: Should we record the checkin for all endpoints or just tokens?
if err := svc.ds.MDMAppleRecordDeclarativeCheckIn(r.Context, dm.UDID, dm.Raw); err != nil {
return nil, ctxerr.Wrap(r.Context, err, "recording declarative checkin")
}
// TODO(sarah): handle tokens
level.Debug(svc.logger).Log("msg", "received tokens request")
return nil, nil
return svc.handleTokens(r.Context, tid)
case dm.Endpoint == "declaration-items":
// TODO(sarah): handle declaration-items
level.Debug(svc.logger).Log("msg", "received declaration-items request")
return nil, nil
return svc.handleDeclarationItems(r.Context, tid)
case dm.Endpoint == "status":
// TODO(roberto): handle status
level.Debug(svc.logger).Log("msg", "received status request")
// TODO(roberto): handle status
return nil, nil
case strings.HasPrefix(dm.Endpoint, "declarations"):
// TODO(sarah): handle declarations
level.Debug(svc.logger).Log("msg", "received declarations request")
parts := strings.Split(dm.Endpoint, "/")
if len(parts) != 3 {
@ -3003,9 +3016,79 @@ func (svc *MDMAppleDDMService) DeclarativeManagement(r *mdm.Request, dm *mdm.Dec
declarationType := parts[1]
declarationIdentifier := parts[2]
level.Debug(svc.logger).Log("msg", "parsed declarations request", "type", declarationType, "identifier", declarationIdentifier)
return nil, nil
// TODO: Validate declarationType?
d, err := svc.ds.MDMAppleDDMDeclarationPayload(r.Context, fleet.MDMAppleDeclarationType("com.apple."+declarationType), declarationIdentifier, tid)
if err != nil {
return nil, ctxerr.Wrap(r.Context, err, "getting declaration")
}
b, err := json.Marshal(d)
if err != nil {
return nil, ctxerr.Wrap(r.Context, err, "marshaling declaration")
}
return b, nil
default:
return nil, ctxerr.New(r.Context, "unrecognized ddm endpoint")
}
}
func (svc *MDMAppleDDMService) handleTokens(ctx context.Context, teamID uint) ([]byte, error) {
tok, err := svc.ds.MDMAppleDDMSynchronizationTokens(ctx, teamID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting synchronization tokens")
}
b, err := json.Marshal(fleet.MDMAppleDDMTokensResponse{
SyncTokens: *tok,
})
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "marshaling synchronization tokens")
}
return b, nil
}
func (svc *MDMAppleDDMService) handleDeclarationItems(ctx context.Context, teamID uint) ([]byte, error) {
di, err := svc.ds.MDMAppleDDMDeclarationItems(ctx, teamID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting synchronization tokens")
}
var dTok string
activations := []fleet.MDMAppleDDMManifest{}
configurations := []fleet.MDMAppleDDMManifest{}
for _, d := range di {
if dTok == "" {
dTok = d.DeclarationsToken
} else if dTok != d.DeclarationsToken {
level.Debug(svc.logger).Log("msg", "inconsistent declarations token", "expected", dTok, "got", d.DeclarationsToken)
}
manifest := fleet.MDMAppleDDMManifest{Identifier: d.Identifier, ServerToken: d.ServerToken}
switch d.DeclarationType {
case string(fleet.MDMAppleDeclarativeActivation):
activations = append(activations, manifest)
case string(fleet.MDMAppleDeclarativeConfiguration):
configurations = append(configurations, manifest)
default:
level.Debug(svc.logger).Log("msg", "unrecognized declaration type", "type", d.DeclarationType)
return nil, ctxerr.New(ctx, "unrecognized declaration type")
}
}
b, err := json.Marshal(fleet.MDMAppleDDMDeclarationItemsResponse{
Declarations: fleet.MDMAppleDDMManifestItems{
Activations: activations,
Configurations: configurations,
Assets: []fleet.MDMAppleDDMManifest{},
Management: []fleet.MDMAppleDDMManifest{},
},
DeclarationsToken: dTok,
})
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "marshaling synchronization tokens")
}
return b, nil
}

View File

@ -12522,23 +12522,246 @@ func (s *integrationMDMTestSuite) TestIsServerBitlockerStatus() {
// TODO(sarah): Build out this test
func (s *integrationMDMTestSuite) TestMDMAppleDeviceManagementRequests() {
t := s.T()
mdmDevice := mdmtest.NewTestMDMClientAppleDirect(mdmtest.AppleEnrollInfo{
SCEPChallenge: s.fleetCfg.MDM.AppleSCEPChallenge,
SCEPURL: s.server.URL + apple_mdm.SCEPPath,
MDMURL: s.server.URL + apple_mdm.MDMPath,
_, mdmDevice := createHostThenEnrollMDM(s.ds, s.server.URL, t)
insertDeclaration := func(t *testing.T, decl fleet.MDMAppleDeclaration) {
stmt := `
INSERT INTO mdm_apple_declarations (
declaration_uuid,
team_id,
identifier,
name,
declaration_type,
declaration,
md5_checksum,
created_at,
uploaded_at
) VALUES (?,?,?,?,?,?,?,?,?)`
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(context.Background(), stmt,
decl.DeclarationUUID,
decl.TeamID,
decl.Identifier,
decl.Name,
decl.DeclarationType,
decl.Declaration,
decl.MD5Checksum,
decl.CreatedAt,
decl.UploadedAt,
)
return err
})
}
// initialize a time to use for our first declaration, subsequent declarations will be
// incremented by a minute
then := time.Now().UTC().Truncate(time.Second).Add(-1 * time.Hour)
// insert a declaration with no team
noTeamDeclsByUUID := map[string]fleet.MDMAppleDeclaration{
"123": {
DeclarationUUID: "123",
TeamID: ptr.Uint(0),
Identifier: "com.example",
Name: "Example",
DeclarationType: fleet.MDMAppleDeclarativeConfiguration,
Declaration: json.RawMessage(`{"foo": "bar"}`),
MD5Checksum: "csum123",
CreatedAt: then,
UploadedAt: then,
},
}
insertDeclaration(t, noTeamDeclsByUUID["123"])
mapDeclsByChecksum := func(byUUID map[string]fleet.MDMAppleDeclaration) map[string]fleet.MDMAppleDeclaration {
byChecksum := make(map[string]fleet.MDMAppleDeclaration)
for _, d := range byUUID {
byChecksum[d.MD5Checksum] = byUUID[d.DeclarationUUID]
}
return byChecksum
}
parseTokensResp := func(r *http.Response) fleet.MDMAppleDDMTokensResponse {
require.NotNil(t, r)
b, err := io.ReadAll(r.Body)
require.NoError(t, err)
defer r.Body.Close()
r.Body = io.NopCloser(bytes.NewBuffer(b))
// t.Log("body", string(b))
// unmarsal the response to make sure it's valid
var tok fleet.MDMAppleDDMTokensResponse
err = json.NewDecoder(r.Body).Decode(&tok)
require.NoError(t, err)
// t.Log("decoded", tok)
return tok
}
parseDeclarationItemsResp := func(r *http.Response) fleet.MDMAppleDDMDeclarationItemsResponse {
require.NotNil(t, r)
b, err := io.ReadAll(r.Body)
require.NoError(t, err)
defer r.Body.Close()
r.Body = io.NopCloser(bytes.NewBuffer(b))
// t.Log("body", string(b))
// unmarsal the response to make sure it's valid
var di fleet.MDMAppleDDMDeclarationItemsResponse
err = json.NewDecoder(r.Body).Decode(&di)
require.NoError(t, err)
// t.Log("decoded", di)
return di
}
parseDeclarationResp := func(r *http.Response, expectedBytes []byte) fleet.MDMAppleDDMDeclarationResponse {
require.NotNil(t, r)
b, err := io.ReadAll(r.Body)
require.NoError(t, err)
defer r.Body.Close()
if expectedBytes != nil {
require.Equal(t, expectedBytes, b)
}
r.Body = io.NopCloser(bytes.NewBuffer(b))
// t.Log("body", string(b))
// unmarsal the response to make sure it's valid
var d fleet.MDMAppleDDMDeclarationResponse
err = json.NewDecoder(r.Body).Decode(&d)
require.NoError(t, err)
// t.Logf("decoded: %+v", d)
return d
}
checkTokensResp := func(t *testing.T, r fleet.MDMAppleDDMTokensResponse, expectedTimestamp time.Time, prevToken string) {
require.Equal(t, expectedTimestamp, r.SyncTokens.Timestamp)
require.NotEmpty(t, r.SyncTokens.DeclarationsToken)
require.NotEqual(t, prevToken, r.SyncTokens.DeclarationsToken)
}
checkDeclarationItemsResp := func(t *testing.T, r fleet.MDMAppleDDMDeclarationItemsResponse, expectedDeclTok string, expectedDeclsByChecksum map[string]fleet.MDMAppleDeclaration) {
require.Equal(t, expectedDeclTok, r.DeclarationsToken)
require.Empty(t, r.Declarations.Activations)
require.Empty(t, r.Declarations.Assets)
require.Empty(t, r.Declarations.Management)
require.Len(t, r.Declarations.Configurations, len(expectedDeclsByChecksum))
for _, m := range r.Declarations.Configurations {
// look up the declaration by the server token (we trim the token to the first seven
// chars to match our keys because response is padded to length 16 with "\u000")
d, ok := expectedDeclsByChecksum[m.ServerToken[0:7]]
require.True(t, ok)
require.Equal(t, d.Identifier, m.Identifier)
}
}
var currDeclToken string // we'll use this to track the expected token across tests
t.Run("Tokens", func(t *testing.T) {
// get tokens, timestamp should be the same as the declaration and token should be non-empty
r, err := mdmDevice.DeclarativeManagement("tokens")
require.NoError(t, err)
parsed := parseTokensResp(r)
checkTokensResp(t, parsed, then, "")
currDeclToken = parsed.SyncTokens.DeclarationsToken
// insert a new declaration
noTeamDeclsByUUID["456"] = fleet.MDMAppleDeclaration{
DeclarationUUID: "456",
TeamID: ptr.Uint(0),
Identifier: "com.example2",
Name: "Example2",
DeclarationType: fleet.MDMAppleDeclarativeConfiguration,
Declaration: json.RawMessage(`{"foo": "baz"}`),
MD5Checksum: "csum456",
CreatedAt: then.Add(1 * time.Minute),
UploadedAt: then.Add(1 * time.Minute),
}
insertDeclaration(t, noTeamDeclsByUUID["456"])
// get tokens again, timestamp and token should have changed
r, err = mdmDevice.DeclarativeManagement("tokens")
require.NoError(t, err)
parsed = parseTokensResp(r)
checkTokensResp(t, parsed, then.Add(1*time.Minute), currDeclToken)
currDeclToken = parsed.SyncTokens.DeclarationsToken
})
err := mdmDevice.Enroll()
require.NoError(t, err)
err = mdmDevice.DeclarativeManagement("tokens")
require.NoError(t, err)
t.Run("DeclarationItems", func(t *testing.T) {
r, err := mdmDevice.DeclarativeManagement("declaration-items")
require.NoError(t, err)
checkDeclarationItemsResp(t, parseDeclarationItemsResp(r), currDeclToken, mapDeclsByChecksum(noTeamDeclsByUUID))
err = mdmDevice.DeclarativeManagement("declaration-items")
require.NoError(t, err)
// insert a new declaration
noTeamDeclsByUUID["789"] = fleet.MDMAppleDeclaration{
DeclarationUUID: "789",
TeamID: ptr.Uint(0),
Identifier: "com.example3",
Name: "Example3",
DeclarationType: fleet.MDMAppleDeclarativeConfiguration,
Declaration: json.RawMessage(`{"foo": "bang"}`),
MD5Checksum: "csum789",
CreatedAt: then.Add(2 * time.Minute),
UploadedAt: then.Add(2 * time.Minute),
}
insertDeclaration(t, noTeamDeclsByUUID["789"])
err = mdmDevice.DeclarativeManagement("status")
require.NoError(t, err)
// get tokens again, timestamp and token should have changed
r, err = mdmDevice.DeclarativeManagement("tokens")
require.NoError(t, err)
toks := parseTokensResp(r)
checkTokensResp(t, toks, then.Add(2*time.Minute), currDeclToken)
currDeclToken = toks.SyncTokens.DeclarationsToken
err = mdmDevice.DeclarativeManagement("declarations/foo/bar")
require.NoError(t, err)
r, err = mdmDevice.DeclarativeManagement("declaration-items")
require.NoError(t, err)
checkDeclarationItemsResp(t, parseDeclarationItemsResp(r), currDeclToken, mapDeclsByChecksum(noTeamDeclsByUUID))
})
t.Run("Status", func(t *testing.T) {
_, err := mdmDevice.DeclarativeManagement("status")
require.NoError(t, err)
})
t.Run("Declaration", func(t *testing.T) {
want := noTeamDeclsByUUID["123"]
wantBytes, err := json.Marshal(want.Declaration)
require.NoError(t, err)
r, err := mdmDevice.DeclarativeManagement(fmt.Sprintf("declarations/%s/%s", "configuration", want.Identifier))
require.NoError(t, err)
_ = parseDeclarationResp(r, wantBytes)
// insert a new declaration
noTeamDeclsByUUID["abc"] = fleet.MDMAppleDeclaration{
DeclarationUUID: "abc",
TeamID: ptr.Uint(0),
Identifier: "com.example4",
Name: "Example4",
DeclarationType: fleet.MDMAppleDeclarativeConfiguration,
Declaration: json.RawMessage(`{
"Type": "com.apple.configuration.test",
"Payload": {"foo":"bar"},
"Identifier": "com.example4",
"ServerToken": "csumabc"
}`),
MD5Checksum: "csumabc",
CreatedAt: then.Add(3 * time.Minute),
UploadedAt: then.Add(3 * time.Minute),
}
insertDeclaration(t, noTeamDeclsByUUID["abc"])
want = noTeamDeclsByUUID["abc"]
wantBytes, err = json.Marshal(want.Declaration)
require.NoError(t, err)
r, err = mdmDevice.DeclarativeManagement(fmt.Sprintf("declarations/%s/%s", "configuration", want.Identifier))
require.NoError(t, err)
d := parseDeclarationResp(r, wantBytes)
require.Equal(t, want.Identifier, d.Identifier)
require.Equal(t, "com.apple.configuration.test", d.Type)
require.Equal(t, json.RawMessage(`{"foo":"bar"}`), d.Payload)
require.Equal(t, want.MD5Checksum, d.ServerToken)
})
}