Skip to content

Commit

Permalink
GetToken check-in message scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
jessepeterson committed Apr 23, 2024
1 parent 0c46445 commit 79e4806
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 0 deletions.
46 changes: 46 additions & 0 deletions mdm/checkin.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,50 @@ type DeclarativeManagement struct {
Raw []byte `plist:"-"` // Original XML plist
}

// TokenParameters is a representation of a "GetTokenRequest.TokenParameters" structure.
// See https://developer.apple.com/documentation/devicemanagement/gettokenrequest/tokenparameters
type TokenParameters struct {
PhoneUDID string
SecurityToken string
WatchUDID string
}

// GetTokenResponse is a representation of a "GetTokenResponse" structure.
// See https://developer.apple.com/documentation/devicemanagement/gettokenresponse
type GetTokenResponse struct {
TokenData []byte
}

// GetToken is a representation of a "GetToken" check-in message type.
// See https://developer.apple.com/documentation/devicemanagement/get_token
type GetToken struct {
Enrollment
MessageType
TokenServiceType string
TokenParameters *TokenParameters `plist:",omitempty"`
Raw []byte `plist:"-"` // Original XML plist
}

// Validate validates a GetToken check-in message.
func (m *GetToken) Validate() error {
if m == nil {
return errors.New("nil GetToken")
}
if m.TokenServiceType == "" {
return errors.New("empty GetToken TokenServiceType")
}
switch m.TokenServiceType {
case "com.apple.maid":
case "com.apple.watch.pairing":
if m.TokenParameters == nil {
return fmt.Errorf("empty GetToken TokenParameters for %s TokenServiceType", m.TokenServiceType)
}
default:
return fmt.Errorf("invalid GetToken TokenServiceType: %s", m.TokenServiceType)
}
return nil
}

// newCheckinMessageForType returns a pointer to a check-in struct for MessageType t
func newCheckinMessageForType(t string, raw []byte) interface{} {
switch t {
Expand All @@ -120,6 +164,8 @@ func newCheckinMessageForType(t string, raw []byte) interface{} {
return &UserAuthenticate{Raw: raw}
case "DeclarativeManagement":
return &DeclarativeManagement{Raw: raw}
case "GetToken":
return &GetToken{Raw: raw}
default:
return nil
}
Expand Down
33 changes: 33 additions & 0 deletions mdm/checkin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,36 @@ func TestTokenUpdate(t *testing.T) {
})
}
}

func TestGetTokenMAID(t *testing.T) {
test := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MessageType</key>
<string>GetToken</string>
<key>UDID</key>
<string>test</string>
<key>TokenServiceType</key>
<string>com.apple.maid</string>
</dict>
</plist>
`
m, err := DecodeCheckin([]byte(test))
if err != nil {
t.Fatal(err)
}
msg, ok := m.(*GetToken)
if !ok {
t.Fatal("incorrect decoded check-in message type")
}
if err := msg.Validate(); err != nil {
t.Fatal(err)
}
if msg, want, have := "invalid UDID", "test", msg.UDID; have != want {
t.Errorf("%s: %q, want: %q", msg, have, want)
}
if msg, want, have := "invalid TokenServiceType", "com.apple.maid", msg.TokenServiceType; have != want {
t.Errorf("%s: %q, want: %q", msg, have, want)
}
}
4 changes: 4 additions & 0 deletions service/certauth/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func (s *NopService) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeMan
return nil, nil
}

func (s *NopService) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
return nil, nil
}

func (s *NopService) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
return nil, nil
}
7 changes: 7 additions & 0 deletions service/certauth/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ func (s *CertAuth) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeManag
return s.next.DeclarativeManagement(r, m)
}

func (s *CertAuth) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
if err := s.validateOrAssociateForExistingEnrollment(r, &m.Enrollment); err != nil {
return nil, err
}
return s.next.GetToken(r, m)
}

func (s *CertAuth) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
if err := s.validateOrAssociateForExistingEnrollment(r, &results.Enrollment); err != nil {
return nil, err
Expand Down
11 changes: 11 additions & 0 deletions service/dump/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package dump

import (
"encoding/base64"
"fmt"
"os"

Expand Down Expand Up @@ -70,6 +71,16 @@ func (svc *Dumper) GetBootstrapToken(r *mdm.Request, m *mdm.GetBootstrapToken) (
return bsToken, err
}

func (svc *Dumper) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
svc.file.Write(m.Raw)
token, err := svc.next.GetToken(r, m)
if token != nil && len(token.TokenData) > 0 {
b64 := base64.StdEncoding.EncodeToString(token.TokenData)
svc.file.WriteString("GetToken TokenData: " + b64 + "\n")
}
return token, err
}

func (svc *Dumper) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
svc.file.Write(results.Raw)
cmd, err := svc.next.CommandAndReportResults(r, results)
Expand Down
14 changes: 14 additions & 0 deletions service/microwebhook/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,17 @@ func (w *MicroWebhook) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeM
}
return nil, postWebhookEvent(r.Context, w.client, w.url, ev)
}

func (w *MicroWebhook) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
ev := &Event{
Topic: "mdm.GetToken",
CreatedAt: time.Now(),
CheckinEvent: &CheckinEvent{
UDID: m.UDID,
EnrollmentID: m.EnrollmentID,
RawPayload: m.Raw,
Params: r.Params,
},
}
return nil, postWebhookEvent(r.Context, w.client, w.url, ev)
}
10 changes: 10 additions & 0 deletions service/multi/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ func (ms *MultiService) DeclarativeManagement(r *mdm.Request, m *mdm.Declarative
return retBytes, err
}

func (ms *MultiService) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
resp, err := ms.svcs[0].GetToken(r, m)
rc := ms.RequestWithContext(r)
ms.runOthers(r.Context, func(svc service.CheckinAndCommandService) error {
_, err := svc.GetToken(rc, m)
return err
})
return resp, err
}

func (ms *MultiService) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
cmd, err := ms.svcs[0].CommandAndReportResults(r, results)
rc := ms.RequestWithContext(r)
Expand Down
25 changes: 25 additions & 0 deletions service/nanomdm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ type Service struct {

// UserAuthenticate processor
ua service.UserAuthenticate

// GetToken handler
gt service.GetToken
}

// normalize generates enrollment IDs that are used by other
Expand Down Expand Up @@ -73,6 +76,13 @@ func WithUserAuthenticate(ua service.UserAuthenticate) Option {
}
}

// WithGetToken configures a GetToken check-in message handler.
func WithGetToken(gt service.GetToken) Option {
return func(s *Service) {
s.gt = gt
}
}

// New returns a new NanoMDM main service.
func New(store storage.ServiceStore, opts ...Option) *Service {
nanomdm := &Service{
Expand Down Expand Up @@ -188,6 +198,21 @@ func (s *Service) DeclarativeManagement(r *mdm.Request, message *mdm.Declarative
return s.dm.DeclarativeManagement(r, message)
}

// GetToken implements the GetToken Check-in message interface.
func (s *Service) GetToken(r *mdm.Request, message *mdm.GetToken) (*mdm.GetTokenResponse, error) {
if err := s.setupRequest(r, &message.Enrollment); err != nil {
return nil, err
}
ctxlog.Logger(r.Context, s.logger).Info(
"msg", "GetToken",
"token_service_type", message.TokenServiceType,
)
if s.gt == nil {
return nil, errors.New("no GetToken handler")
}
return s.gt.GetToken(r, message)
}

// CommandAndReportResults command report and next-command request implementation.
func (s *Service) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
if err := s.setupRequest(r, &results.Enrollment); err != nil {
Expand Down
16 changes: 16 additions & 0 deletions service/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ func CheckinRequest(svc Checkin, r *mdm.Request, bodyBytes []byte) ([]byte, erro
if err != nil {
err = fmt.Errorf("declarativemanagement service: %w", err)
}
case *mdm.GetToken:
if err := m.Validate(); err != nil {
return nil, fmt.Errorf("gettoken validate: %w", err)
}
resp, err := svc.GetToken(r, m)
if err != nil {
return nil, fmt.Errorf("gettoken service: %w", err)
}
if resp == nil {
return nil, errors.New("gettoken service: no response")
}
respBytes, err = plist.Marshal(resp)
if err != nil {
return nil, fmt.Errorf("gettoken marshal: %w", err)
}
return respBytes, nil
default:
return nil, errors.New("unhandled check-in request type")
}
Expand Down
7 changes: 7 additions & 0 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ type UserAuthenticate interface {
UserAuthenticate(*mdm.Request, *mdm.UserAuthenticate) ([]byte, error)
}

// GetToken is the interface for handling a GetToken check-in message.
// See https://developer.apple.com/documentation/devicemanagement/get_token
type GetToken interface {
GetToken(*mdm.Request, *mdm.GetToken) (*mdm.GetTokenResponse, error)
}

// Checkin represents the various check-in requests.
// See https://developer.apple.com/documentation/devicemanagement/check-in
type Checkin interface {
Expand All @@ -26,6 +32,7 @@ type Checkin interface {
GetBootstrapToken(*mdm.Request, *mdm.GetBootstrapToken) (*mdm.BootstrapToken, error)
UserAuthenticate
DeclarativeManagement
GetToken
}

// CommandAndReportResults represents the command report and next-command request.
Expand Down

0 comments on commit 79e4806

Please sign in to comment.