From ed6ceefb2c6f403459c67ee42b41cb00285ef527 Mon Sep 17 00:00:00 2001 From: Kory Prince Date: Fri, 17 Oct 2025 14:48:09 -0500 Subject: [PATCH 1/3] Initial draft of queue inspect and delete APIs --- api/types.go | 9 ++++ docs/openapi.yaml | 106 ++++++++++++++++++++++++++++++++++++++++++++++ storage/queue.go | 11 +++++ 3 files changed, 126 insertions(+) diff --git a/api/types.go b/api/types.go index 59d02efe..f4841c05 100644 --- a/api/types.go +++ b/api/types.go @@ -85,3 +85,12 @@ func (r *APIResult) Error() error { return nil } + +// QueueAPIResult is the result of queue APIs. +type QueueAPIResult struct { + // Status is the per-enrollment ID results of queue APIs. + // Map key is the enrollment ID. + Status map[string]*Error `json:"status,omitempty"` + // Error is present if there was a general error with the queue API call. + Error *Error `json:"error,omitempty"` +} diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 81e25c3a..94cdfe06 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -102,6 +102,59 @@ paths: schema: type: string example: '1' + /v1/queue/{id}: + get: + description: Retrieve queued MDM commands for a specific enrollment ID with pagination support + security: + - basicAuth: [] + parameters: + - name: id + in: path + description: Enrollment ID of device- or user-channel enrollment. Typically a UUID-looking identifier. + required: true + schema: + type: string + example: '299BD49-1A0C-422C-B285-2E4FF087C673' + - name: cursor + in: query + description: Pagination cursor for retrieving the next page of results. Omit or leave empty to start from the beginning. + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of commands to retrieve per page. + required: false + schema: + type: integer + example: 100 + responses: + '200': + description: Successfully retrieved queued commands + content: + application/json: + schema: + $ref: '#/components/schemas/QueuedCommandsResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + description: Error retrieving queued commands + /v1/queue/{id*}: + delete: + description: Clear all queued MDM commands for one or more enrollment IDs + security: + - basicAuth: [] + parameters: + - $ref: '#/components/parameters/idParam' + responses: + '204': + description: Successfully cleared queued commands for all enrollment IDs + '207': + $ref: '#/components/responses/QueueAPIResultSomeFailed' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/QueueAPIResultAllFailed' /v1/escrowkeyunlock: post: description: "Perform an Escrow Key Unlock against Apple's API. Uses the APNs certificate of the provided topic for mTLS authentication. Note that despite all parameters being in the HTTP body (form) this endpoint moves the appropriate parameters to the URL query parameters per Apple's documentation. The response body, status, and headers are handed straight through from the Apple endpoint." @@ -310,6 +363,18 @@ components: application/json: schema: $ref: '#/components/schemas/APIResult' + QueueAPIResultSomeFailed: + description: Some requests succeeded and some failed. Returns JSON queue API response object including errors. + content: + application/json: + schema: + $ref: '#/components/schemas/QueueAPIResult' + QueueAPIResultAllFailed: + description: All requests failed. Returns JSON queue API response object including errors. + content: + application/json: + schema: + $ref: '#/components/schemas/QueueAPIResult' schemas: APIResult: type: object @@ -486,3 +551,44 @@ components: type: string description: Full name of the user. example: 'Jane Smith' + QueuedCommandsResponse: + type: object + properties: + commands: + type: array + description: Array of queued MDM commands + items: + type: object + properties: + command_uuid: + type: string + format: uuid + example: 'fedd659e-fc3c-4e35-8bb1-c8f51ae542a5' + request_type: + type: string + example: 'ProfileList' + command: + type: string + format: byte + description: The full MDM command plist data, base64-encoded. + example: |- + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KPGtleT5Db21tYW5kPC9rZXk+CjxkaWN0PgogICAgPGtleT5SZXF1ZXN0VHlwZTwva2V5PgogICAgPHN0cmluZz5Qcm9maWxlTGlzdDwvc3RyaW5nPgo8L2RpY3Q+CjxrZXk+Q29tbWFuZFVVSUQ8L2tleT4KPHN0cmluZz5mZWRkNjU5ZS1mYzNjLTRlMzUtOGJiMS1jOGY1MWFlNTQyYTU8L3N0cmluZz4KPC9kaWN0Pgo8L3BsaXN0Pg== + next_cursor: + type: string + description: Cursor for retrieving the next page of results. Empty if no more results available. + QueueAPIResult: + type: object + description: Result of queue API operations + properties: + status: + type: object + description: Per-enrollment ID error status. Map key is the enrollment ID. + properties: + $id: + type: string + description: Error message for this enrollment ID + example: + '299BD49-1A0C-422C-B285-2E4FF087C673': 'queue not found' + 'E2E4A8EB-45EE-488D-B9D7-4CC3B1C40699': 'database error' + error: + type: string diff --git a/storage/queue.go b/storage/queue.go index 30c99bde..f6c77eb8 100644 --- a/storage/queue.go +++ b/storage/queue.go @@ -17,3 +17,14 @@ type CommandAndReportResultsStore interface { type CommandEnqueuer interface { EnqueueCommand(ctx context.Context, id []string, cmd *mdm.Command) (map[string]error, error) } + +// CommandQueueAPIStore can retrieve and clear queued commands by enrollment ID. +type CommandQueueAPIStore interface { + // RetrieveQueuedCommands retrieves queued commands for the given enrollment ID. + // The cursor is used for pagination; an empty cursor retrieves from the start. + // Limit specifies the maximum number of commands to retrieve. If limit is zero or negative, all commands are retrieved. + // The retrieved commands and the next cursor (if more commands are available) are returned, or an error if any. + RetrieveQueuedCommands(ctx context.Context, id, cursor string, limit int) (commands []*mdm.Command, nextCursor string, err error) + // ClearQueueByID clears all queued commands for the given enrollment ID. + ClearQueueByID(ctx context.Context, id string) error +} From a66a315efdbf63f5d20f0398b73acbb18a1208d5 Mon Sep 17 00:00:00 2001 From: Kory Prince Date: Sat, 25 Oct 2025 14:09:52 -0500 Subject: [PATCH 2/3] update spec to match enrollements api --- docs/openapi.yaml | 29 +++++++++++++++++++---------- storage/queue.go | 25 +++++++++++++++++++++---- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 94cdfe06..064fd06c 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -104,7 +104,7 @@ paths: example: '1' /v1/queue/{id}: get: - description: Retrieve queued MDM commands for a specific enrollment ID with pagination support + description: Retrieve queued MDM commands for a specific enrollment ID. Supports both offset-based and cursor-based pagination via query parameters. security: - basicAuth: [] parameters: @@ -115,26 +115,34 @@ paths: schema: type: string example: '299BD49-1A0C-422C-B285-2E4FF087C673' - - name: cursor + - name: limit in: query - description: Pagination cursor for retrieving the next page of results. Omit or leave empty to start from the beginning. + description: Maximum number of commands to return in the response. If zero or omitted, all commands are returned. required: false schema: - type: string - - name: limit + type: integer + example: 100 + - name: offset in: query - description: Maximum number of commands to retrieve per page. + description: Offset for offset-based pagination. Use offset = 0 for the first request. Mutually exclusive with cursor-based pagination. required: false schema: type: integer - example: 100 + example: 0 + - name: cursor + in: query + description: Cursor for cursor-based pagination. The first request should leave this empty. Subsequent requests should set this to the next_cursor value from the previous response. Mutually exclusive with offset-based pagination. + required: false + schema: + type: string + example: '' responses: '200': description: Successfully retrieved queued commands content: application/json: schema: - $ref: '#/components/schemas/QueuedCommandsResponse' + $ref: '#/components/schemas/QueueQueryResult' '401': $ref: '#/components/responses/UnauthorizedError' '500': @@ -551,8 +559,9 @@ components: type: string description: Full name of the user. example: 'Jane Smith' - QueuedCommandsResponse: + QueueQueryResult: type: object + description: Result of a queue query containing commands and pagination information. properties: commands: type: array @@ -575,7 +584,7 @@ components: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KPGtleT5Db21tYW5kPC9rZXk+CjxkaWN0PgogICAgPGtleT5SZXF1ZXN0VHlwZTwva2V5PgogICAgPHN0cmluZz5Qcm9maWxlTGlzdDwvc3RyaW5nPgo8L2RpY3Q+CjxrZXk+Q29tbWFuZFVVSUQ8L2tleT4KPHN0cmluZz5mZWRkNjU5ZS1mYzNjLTRlMzUtOGJiMS1jOGY1MWFlNTQyYTU8L3N0cmluZz4KPC9kaWN0Pgo8L3BsaXN0Pg== next_cursor: type: string - description: Cursor for retrieving the next page of results. Empty if no more results available. + description: Cursor for pagination. If non-empty, more commands may be fetched by setting this value in the Cursor field (query parameter) of a subsequent request. QueueAPIResult: type: object description: Result of queue API operations diff --git a/storage/queue.go b/storage/queue.go index f6c77eb8..d63430d6 100644 --- a/storage/queue.go +++ b/storage/queue.go @@ -18,13 +18,30 @@ type CommandEnqueuer interface { EnqueueCommand(ctx context.Context, id []string, cmd *mdm.Command) (map[string]error, error) } +// QueueQuery represents a query for queued commands. +type QueueQuery struct { + // ID is the enrollment ID to retrieve queued commands for. + ID string + // Pagination supports cursor-based pagination. + Pagination Pagination +} + +// QueueQueryResult contains the result of a queue query. +type QueueQueryResult struct { + Commands []*mdm.Command `json:"commands"` + + // NextCursor is a cursor for pagination. If non-empty, more commands may be fetched by + // setting this value in the Cursor field of a subsequent request. + NextCursor string `json:"next_cursor,omitempty"` +} + // CommandQueueAPIStore can retrieve and clear queued commands by enrollment ID. type CommandQueueAPIStore interface { // RetrieveQueuedCommands retrieves queued commands for the given enrollment ID. - // The cursor is used for pagination; an empty cursor retrieves from the start. - // Limit specifies the maximum number of commands to retrieve. If limit is zero or negative, all commands are retrieved. - // The retrieved commands and the next cursor (if more commands are available) are returned, or an error if any. - RetrieveQueuedCommands(ctx context.Context, id, cursor string, limit int) (commands []*mdm.Command, nextCursor string, err error) + // If no commands are queued, an empty QueueQueryResult is returned with no error. + // Implementations should not set internal error fields; errors should be returned via the error return value. + RetrieveQueuedCommands(ctx context.Context, req *QueueQuery) (*QueueQueryResult, error) + // ClearQueueByID clears all queued commands for the given enrollment ID. ClearQueueByID(ctx context.Context, id string) error } From 7742fb2209f4bdb9cd8fac0fbe59000ff4392e5e Mon Sep 17 00:00:00 2001 From: Kory Prince Date: Tue, 27 Jan 2026 18:57:01 -0600 Subject: [PATCH 3/3] make api match NanoDEP's api --- docs/openapi.yaml | 26 ++++++++++++++++++++++---- storage/queue.go | 9 +++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 064fd06c..55a17cac 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -117,11 +117,12 @@ paths: example: '299BD49-1A0C-422C-B285-2E4FF087C673' - name: limit in: query - description: Maximum number of commands to return in the response. If zero or omitted, all commands are returned. + description: Maximum number of commands to return in the response. required: false schema: type: integer example: 100 + default: 100 - name: offset in: query description: Offset for offset-based pagination. Use offset = 0 for the first request. Mutually exclusive with cursor-based pagination. @@ -138,15 +139,29 @@ paths: example: '' responses: '200': - description: Successfully retrieved queued commands + description: Success. Returns queued commands for the enrollment. content: application/json: schema: $ref: '#/components/schemas/QueueQueryResult' + '400': + description: "Failure: bad request; likely cause is a malformed request query." + content: + application/json: + schema: + $ref: '#/components/schemas/QueueQueryResult' + example: + error: "invalid request: both cursor and offset set" '401': $ref: '#/components/responses/UnauthorizedError' '500': - description: Error retrieving queued commands + description: "Unexpected server error; try again later." + content: + application/json: + schema: + $ref: '#/components/schemas/QueueQueryResult' + example: + error: "database connection failed" /v1/queue/{id*}: delete: description: Clear all queued MDM commands for one or more enrollment IDs @@ -584,7 +599,10 @@ components: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KPGtleT5Db21tYW5kPC9rZXk+CjxkaWN0PgogICAgPGtleT5SZXF1ZXN0VHlwZTwva2V5PgogICAgPHN0cmluZz5Qcm9maWxlTGlzdDwvc3RyaW5nPgo8L2RpY3Q+CjxrZXk+Q29tbWFuZFVVSUQ8L2tleT4KPHN0cmluZz5mZWRkNjU5ZS1mYzNjLTRlMzUtOGJiMS1jOGY1MWFlNTQyYTU8L3N0cmluZz4KPC9kaWN0Pgo8L3BsaXN0Pg== next_cursor: type: string - description: Cursor for pagination. If non-empty, more commands may be fetched by setting this value in the Cursor field (query parameter) of a subsequent request. + description: For storage backends that support cursor-based pagination this will contain the next cursor value. + error: + type: string + description: Error message if there was an error processing the request. QueueAPIResult: type: object description: Result of queue API operations diff --git a/storage/queue.go b/storage/queue.go index d63430d6..ac33e19d 100644 --- a/storage/queue.go +++ b/storage/queue.go @@ -23,16 +23,17 @@ type QueueQuery struct { // ID is the enrollment ID to retrieve queued commands for. ID string // Pagination supports cursor-based pagination. - Pagination Pagination + Pagination *Pagination } // QueueQueryResult contains the result of a queue query. type QueueQueryResult struct { Commands []*mdm.Command `json:"commands"` - // NextCursor is a cursor for pagination. If non-empty, more commands may be fetched by - // setting this value in the Cursor field of a subsequent request. - NextCursor string `json:"next_cursor,omitempty"` + PaginationNextCursor + + // Error contains an error message if there was an error processing the request. + Error string `json:"error,omitempty"` } // CommandQueueAPIStore can retrieve and clear queued commands by enrollment ID.