Skip to content

Commit

Permalink
Implement a TokenUpdate "tally" for each backend. This is a simple co…
Browse files Browse the repository at this point in the history
…unter that is bumped for each TokenUpdate that is later read for the TokenUpdate webhook in a new attribute. Resolves #16.
  • Loading branch information
jessepeterson committed Jul 5, 2021
1 parent 4d5561f commit cc53d28
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 7 deletions.
2 changes: 1 addition & 1 deletion cmd/nanomdm/main.go
Expand Up @@ -96,7 +96,7 @@ func main() {
if !*flDisableMDM {
var mdmService service.CheckinAndCommandService = nano
if *flWebhook != "" {
webhookService := microwebhook.New(*flWebhook)
webhookService := microwebhook.New(*flWebhook, mdmStorage)
mdmService = multi.New(logger.With("service", "multi"), mdmService, webhookService)
}
certAuthOpts := []certauth.Option{certauth.WithLogger(logger.With("service", "certauth"))}
Expand Down
4 changes: 4 additions & 0 deletions service/microwebhook/event.go
Expand Up @@ -25,4 +25,8 @@ type CheckinEvent struct {
EnrollmentID string `json:"enrollment_id,omitempty"`
Params map[string]string `json:"url_params"`
RawPayload []byte `json:"raw_payload"`

// signals which tokenupdate this is to be able to tell whether this
// is the initial enrollment vs. a following tokenupdate
TokenUpdateTally *int `json:"token_update_tally,omitempty"`
}
12 changes: 11 additions & 1 deletion service/microwebhook/service.go
Expand Up @@ -6,17 +6,20 @@ import (
"time"

"github.com/micromdm/nanomdm/mdm"
"github.com/micromdm/nanomdm/storage"
)

type MicroWebhook struct {
url string
client *http.Client
store storage.TokenUpdateTallyStore
}

func New(url string) *MicroWebhook {
func New(url string, store storage.TokenUpdateTallyStore) *MicroWebhook {
return &MicroWebhook{
url: url,
client: http.DefaultClient,
store: store,
}
}

Expand Down Expand Up @@ -45,6 +48,13 @@ func (w *MicroWebhook) TokenUpdate(r *mdm.Request, m *mdm.TokenUpdate) error {
Params: r.Params,
},
}
if w.store != nil {
tally, err := w.store.RetrieveTokenUpdateTally(r.Context, r.ID)
if err != nil {
return err
}
ev.CheckinEvent.TokenUpdateTally = &tally
}
return postWebhookEvent(r.Context, w.client, w.url, ev)
}

Expand Down
1 change: 1 addition & 0 deletions storage/all.go
Expand Up @@ -8,4 +8,5 @@ type AllStorage interface {
CommandEnqueuer
CertAuthStore
StoreMigrator
TokenUpdateTallyStore
}
9 changes: 9 additions & 0 deletions storage/allmulti/allmulti.go
@@ -1,6 +1,8 @@
package allmulti

import (
"context"

"github.com/micromdm/nanomdm/log"
"github.com/micromdm/nanomdm/mdm"
"github.com/micromdm/nanomdm/storage"
Expand Down Expand Up @@ -70,6 +72,13 @@ func (ms *MultiAllStorage) StoreTokenUpdate(r *mdm.Request, msg *mdm.TokenUpdate
return err
}

func (ms *MultiAllStorage) RetrieveTokenUpdateTally(ctx context.Context, id string) (int, error) {
val, err := ms.execStores(func(s storage.AllStorage) (interface{}, error) {
return s.RetrieveTokenUpdateTally(ctx, id)
})
return val.(int), err
}

func (ms *MultiAllStorage) StoreUserAuthenticate(r *mdm.Request, msg *mdm.UserAuthenticate) error {
_, err := ms.execStores(func(s storage.AllStorage) (interface{}, error) {
return nil, s.StoreUserAuthenticate(r, msg)
Expand Down
37 changes: 37 additions & 0 deletions storage/file/file.go
Expand Up @@ -2,10 +2,12 @@
package file

import (
"context"
"errors"
"io/ioutil"
"os"
"path"
"strconv"

"github.com/micromdm/nanomdm/cryptoutil"
"github.com/micromdm/nanomdm/mdm"
Expand All @@ -20,6 +22,8 @@ const (
DisabledFilename = "Disabled"
BootstrapTokenFile = "BootstrapToken.dat"

TokenUpdateTallyFilename = "TokenUpdate.tally.txt"

UserAuthFilename = "UserAuthenticate.plist"
UserAuthDigestFilename = "UserAuthenticate.Digest.plist"

Expand Down Expand Up @@ -94,6 +98,28 @@ func (e *enrollment) fileExists(name string) (bool, error) {
return true, nil
}

func (e *enrollment) bumpNumericFile(name string) error {
ctr, err := e.readNumericFile(name)
if err != nil {
return err
}
ctr += 1
return e.writeFile(name, []byte(strconv.Itoa(ctr)))
}

func (e *enrollment) resetNumericFile(name string) error {
return e.writeFile(name, []byte{48})
}

func (e *enrollment) readNumericFile(name string) (int, error) {
val, err := e.readFile(name)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return 0, err
}
ctr, _ := strconv.Atoi(string(val))
return ctr, nil
}

// assocSubEnrollment writes an empty file of the sub (user) enrollment for tracking.
func (e *enrollment) assocSubEnrollment(id string) error {
subPath := e.dirPrefix(SubEnrollmentPathname)
Expand Down Expand Up @@ -162,13 +188,21 @@ func (s *FileStorage) StoreTokenUpdate(r *mdm.Request, msg *mdm.TokenUpdate) err
if err := e.writeFile(TokenUpdateFilename, []byte(msg.Raw)); err != nil {
return err
}
if err := e.bumpNumericFile(TokenUpdateTallyFilename); err != nil {
return err
}
// delete the disabled flag to let signify this enrollment is enabled
if err := os.Remove(e.dirPrefix(DisabledFilename)); err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
return nil
}

func (s *FileStorage) RetrieveTokenUpdateTally(_ context.Context, id string) (int, error) {
e := s.newEnrollment(id)
return e.readNumericFile(TokenUpdateTallyFilename)
}

func (s *FileStorage) StoreUserAuthenticate(r *mdm.Request, msg *mdm.UserAuthenticate) error {
e := s.newEnrollment(r.ID)
filename := UserAuthFilename
Expand All @@ -195,6 +229,9 @@ func (s *FileStorage) Disable(r *mdm.Request) error {
if err := e.writeFile(DisabledFilename, nil); err != nil {
return err
}
if err := e.resetNumericFile(TokenUpdateTallyFilename); err != nil {
return err
}
}
return e.removeSubEnrollments()
}
20 changes: 16 additions & 4 deletions storage/mysql/mysql.go
Expand Up @@ -2,6 +2,7 @@
package mysql

import (
"context"
"database/sql"
"errors"

Expand Down Expand Up @@ -124,9 +125,9 @@ func (s *MySQLStorage) StoreTokenUpdate(r *mdm.Request, msg *mdm.TokenUpdate) er
_, err = s.db.ExecContext(
r.Context, `
INSERT INTO enrollments
(id, device_id, user_id, type, topic, push_magic, token_hex)
(id, device_id, user_id, type, topic, push_magic, token_hex, token_update_tally)
VALUES
(?, ?, ?, ?, ?, ?, ?) AS new
(?, ?, ?, ?, ?, ?, ?, 1) AS new
ON DUPLICATE KEY
UPDATE
device_id = new.device_id,
Expand All @@ -135,7 +136,8 @@ UPDATE
topic = new.topic,
push_magic = new.push_magic,
token_hex = new.token_hex,
enabled = 1;`,
enabled = 1,
enrollments.token_update_tally = enrollments.token_update_tally + 1;`,
r.ID,
deviceId,
nullEmptyString(userId),
Expand All @@ -147,6 +149,16 @@ UPDATE
return err
}

func (s *MySQLStorage) RetrieveTokenUpdateTally(ctx context.Context, id string) (int, error) {
var tally int
err := s.db.QueryRowContext(
ctx,
`SELECT token_update_tally FROM enrollments WHERE id = ?;`,
id,
).Scan(&tally)
return tally, err
}

func (s *MySQLStorage) StoreUserAuthenticate(r *mdm.Request, msg *mdm.UserAuthenticate) error {
colName := "user_authenticate"
colAtName := "user_authenticate_at"
Expand Down Expand Up @@ -184,7 +196,7 @@ func (s *MySQLStorage) Disable(r *mdm.Request) error {
}
_, err := s.db.ExecContext(
r.Context,
`UPDATE enrollments SET enabled = 0 WHERE device_id = ? AND enabled = 1;`,
`UPDATE enrollments SET enabled = 0, token_update_tally = 0 WHERE device_id = ? AND enabled = 1;`,
r.ID,
)
return err
Expand Down
1 change: 1 addition & 0 deletions storage/mysql/schema.00003.sql
@@ -0,0 +1 @@
ALTER TABLE enrollments ADD COLUMN token_update_tally INTEGER NOT NULL DEFAULT 1;
3 changes: 2 additions & 1 deletion storage/mysql/schema.sql
Expand Up @@ -99,7 +99,8 @@ CREATE TABLE enrollments (
push_magic VARCHAR(127) NOT NULL,
token_hex VARCHAR(255) NOT NULL, -- TODO: Perhaps just CHAR(64)?

enabled BOOLEAN NOT NULL DEFAULT 1,
enabled BOOLEAN NOT NULL DEFAULT 1,
token_update_tally INTEGER NOT NULL DEFAULT 1,

created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
Expand Down
5 changes: 5 additions & 0 deletions storage/storage.go
Expand Up @@ -74,3 +74,8 @@ type StoreMigrator interface {
// follow the device channel TokenUpdate.
RetrieveMigrationCheckins(context.Context, chan<- interface{}) error
}

// TokenUpdateTallyStore retrieves the TokenUpdate tally (count) for an id
type TokenUpdateTallyStore interface {
RetrieveTokenUpdateTally(ctx context.Context, id string) (int, error)
}

0 comments on commit cc53d28

Please sign in to comment.