diff --git a/server/mdm/nanodep/godep/device.go b/server/mdm/nanodep/godep/device.go index b445aa5d80..981648219e 100644 --- a/server/mdm/nanodep/godep/device.go +++ b/server/mdm/nanodep/godep/device.go @@ -94,6 +94,26 @@ func (c *Client) SyncDevices(ctx context.Context, name string, opts ...DeviceReq return resp, c.doWithAfterHook(ctx, name, http.MethodPost, "/devices/sync", req, resp) } +// GetDevicesDetails uses the Apple "Get Device Details" API endpoint to +// retrieve the details (such as its assigned enrollment profile UUID) for the +// specified device, identified by its serial number. +// See https://developer.apple.com/documentation/devicemanagement/get_device_details +func (c *Client) GetDeviceDetails(ctx context.Context, name, serialNumber string) (*Device, error) { + type request struct { + Devices []string `json:"devices"` + } + type response struct { + Devices map[string]*Device `json:"devices"` + } + resp := new(response) + if err := c.doWithAfterHook(ctx, name, http.MethodPost, "/devices", request{ + Devices: []string{serialNumber}, + }, resp); err != nil { + return nil, err + } + return resp.Devices[serialNumber], nil +} + // IsCursorExhausted returns true if err is a DEP "exhausted cursor" error. func IsCursorExhausted(err error) bool { return httpErrorContains(err, http.StatusBadRequest, "EXHAUSTED_CURSOR") diff --git a/server/mdm/nanodep/godep/profile.go b/server/mdm/nanodep/godep/profile.go index 267b1448c2..c39d92d9a4 100644 --- a/server/mdm/nanodep/godep/profile.go +++ b/server/mdm/nanodep/godep/profile.go @@ -2,7 +2,9 @@ package godep import ( "context" + "encoding/json" "net/http" + "net/url" ) // Profile corresponds to the Apple DEP API "Profile" structure. @@ -75,3 +77,12 @@ func (c *Client) DefineProfile(ctx context.Context, name string, profile *Profil resp := new(ProfileResponse) return resp, c.doWithAfterHook(ctx, name, http.MethodPost, "/profile", profile, resp) } + +// GetProfile uses the Apple "Get a Profile" API endpoint to get the details +// for the specified profile UUID. +// See https://developer.apple.com/documentation/devicemanagement/get_a_profile +func (c *Client) GetProfile(ctx context.Context, name, profileUUID string) (*json.RawMessage, error) { + resp := &json.RawMessage{} + qs := url.Values{"profile_uuid": {profileUUID}} + return resp, c.doWithAfterHook(ctx, name, http.MethodGet, "/profile?"+qs.Encode(), nil, resp) +} diff --git a/tools/mdm/apple/getaccount/main.go b/tools/mdm/apple/applebmapi/main.go similarity index 73% rename from tools/mdm/apple/getaccount/main.go rename to tools/mdm/apple/applebmapi/main.go index dec6a4ef8f..4a7d95b74f 100644 --- a/tools/mdm/apple/getaccount/main.go +++ b/tools/mdm/apple/applebmapi/main.go @@ -1,6 +1,6 @@ -// Command getaccount takes an Apple Business Manager server token in decrypted +// Command applebmapi takes an Apple Business Manager server token in decrypted // JSON format and calls the Apple BM API to retrieve and print the account -// information. +// information or the specified enrollment profile. // // Was implemented to test out https://github.com/fleetdm/fleet/issues/7515#issuecomment-1330889768, // and can still be useful for debugging purposes. @@ -27,12 +27,18 @@ import ( func main() { mysqlAddr := flag.String("mysql", "localhost:3306", "mysql address") appleBMToken := flag.String("apple-bm-token", "", "path to (decrypted) Apple BM token") + profileUUID := flag.String("profile-uuid", "", "the Apple profile UUID to retrieve") + serialNum := flag.String("serial-number", "", "serial number of a device to get the device details") flag.Parse() if *appleBMToken == "" { log.Fatal("must provide Apple BM token") } + if *profileUUID != "" && *serialNum != "" { + log.Fatal("only one of -profile-uuid or -serial-number must be provided") + } + tok, err := os.ReadFile(*appleBMToken) if err != nil { log.Fatal(err) @@ -64,11 +70,18 @@ func main() { if err != nil { log.Fatal(err) } - depClient := godep.NewClient(depStorage, fleethttp.NewClient()) ctx := context.Background() - res, err := depClient.AccountDetail(ctx, apple_mdm.DEPName) + var res any + switch { + case *profileUUID != "": + res, err = depClient.GetProfile(ctx, apple_mdm.DEPName, *profileUUID) + case *serialNum != "": + res, err = depClient.GetDeviceDetails(ctx, apple_mdm.DEPName, *serialNum) + default: + res, err = depClient.AccountDetail(ctx, apple_mdm.DEPName) + } if err != nil { log.Fatal(err) }