mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-12 04:55:39 +08:00
Wraps mdm.Responses into pubsub events and sends them downstream This PR does not yet implement the full mdm.Response as an event, only the metadata. The device db adds a subscription to mdm responses and updates the LastCheckin key. Closes #251
356 lines
9.7 KiB
Go
356 lines
9.7 KiB
Go
package device
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/boltdb/bolt"
|
|
"github.com/micromdm/micromdm/checkin"
|
|
"github.com/micromdm/micromdm/connect"
|
|
"github.com/micromdm/micromdm/depsync"
|
|
"github.com/micromdm/micromdm/pubsub"
|
|
"github.com/pkg/errors"
|
|
uuid "github.com/satori/go.uuid"
|
|
)
|
|
|
|
const (
|
|
DeviceBucket = "mdm.Devices"
|
|
|
|
// The deviceIndexBucket index bucket stores serial number and UDID references
|
|
// to the device uuid.
|
|
deviceIndexBucket = "mdm.DeviceIdx"
|
|
|
|
DeviceEnrolledTopic = "mdm.DeviceEnrolled"
|
|
)
|
|
|
|
type DB struct {
|
|
*bolt.DB
|
|
}
|
|
|
|
func NewDB(db *bolt.DB, pubsubSvc pubsub.PublishSubscriber) (*DB, error) {
|
|
err := db.Update(func(tx *bolt.Tx) error {
|
|
_, err := tx.CreateBucketIfNotExists([]byte(deviceIndexBucket))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = tx.CreateBucketIfNotExists([]byte(DeviceBucket))
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "creating %s bucket", DeviceBucket)
|
|
}
|
|
datastore := &DB{
|
|
DB: db,
|
|
}
|
|
if pubsubSvc == nil { // don't start the poller without pubsub.
|
|
return datastore, nil
|
|
}
|
|
if err := datastore.pollCheckin(pubsubSvc); err != nil {
|
|
return nil, err
|
|
}
|
|
return datastore, nil
|
|
}
|
|
|
|
func (db *DB) List() ([]Device, error) {
|
|
// TODO add filter/limit with ForEach
|
|
var devices []Device
|
|
err := db.View(func(tx *bolt.Tx) error {
|
|
b := tx.Bucket([]byte(DeviceBucket))
|
|
c := b.Cursor()
|
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
|
var dev Device
|
|
if err := UnmarshalDevice(v, &dev); err != nil {
|
|
return err
|
|
}
|
|
devices = append(devices, dev)
|
|
}
|
|
return nil
|
|
})
|
|
return devices, err
|
|
}
|
|
|
|
func (db *DB) Save(dev *Device) error {
|
|
tx, err := db.DB.Begin(true)
|
|
if err != nil {
|
|
return errors.Wrap(err, "begin transaction")
|
|
}
|
|
bkt := tx.Bucket([]byte(DeviceBucket))
|
|
if bkt == nil {
|
|
return fmt.Errorf("bucket %q not found!", DeviceBucket)
|
|
}
|
|
devproto, err := MarshalDevice(dev)
|
|
if err != nil {
|
|
return errors.Wrap(err, "marshalling device")
|
|
}
|
|
|
|
// store an array of indices to reference the UUID, which will be the
|
|
// key used to store the actual device.
|
|
indexes := []string{dev.UDID, dev.SerialNumber}
|
|
idxBucket := tx.Bucket([]byte(deviceIndexBucket))
|
|
if idxBucket == nil {
|
|
return fmt.Errorf("bucket %q not found!", deviceIndexBucket)
|
|
}
|
|
for _, idx := range indexes {
|
|
if idx == "" {
|
|
continue
|
|
}
|
|
key := []byte(idx)
|
|
if err := idxBucket.Put(key, []byte(dev.UUID)); err != nil {
|
|
return errors.Wrap(err, "put device to boltdb")
|
|
}
|
|
}
|
|
|
|
key := []byte(dev.UUID)
|
|
if err := bkt.Put(key, devproto); err != nil {
|
|
return errors.Wrap(err, "put device to boltdb")
|
|
}
|
|
return tx.Commit()
|
|
}
|
|
|
|
type notFound struct {
|
|
ResourceType string
|
|
Message string
|
|
}
|
|
|
|
func (e *notFound) Error() string {
|
|
return fmt.Sprintf("not found: %s %s", e.ResourceType, e.Message)
|
|
}
|
|
|
|
func (db *DB) DeviceByUDID(udid string) (*Device, error) {
|
|
var dev Device
|
|
err := db.View(func(tx *bolt.Tx) error {
|
|
b := tx.Bucket([]byte(DeviceBucket))
|
|
ib := tx.Bucket([]byte(deviceIndexBucket))
|
|
idx := ib.Get([]byte(udid))
|
|
if idx == nil {
|
|
return ¬Found{"Device", fmt.Sprintf("udid %s", udid)}
|
|
}
|
|
v := b.Get(idx)
|
|
if idx == nil {
|
|
return ¬Found{"Device", fmt.Sprintf("uuid %s", string(idx))}
|
|
}
|
|
return UnmarshalDevice(v, &dev)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &dev, nil
|
|
}
|
|
|
|
func (db *DB) DeviceBySerial(serial string) (*Device, error) {
|
|
var dev Device
|
|
err := db.View(func(tx *bolt.Tx) error {
|
|
b := tx.Bucket([]byte(DeviceBucket))
|
|
ib := tx.Bucket([]byte(deviceIndexBucket))
|
|
idx := ib.Get([]byte(serial))
|
|
if idx == nil {
|
|
return ¬Found{"Device", fmt.Sprintf("serial %s", serial)}
|
|
}
|
|
v := b.Get(idx)
|
|
if idx == nil {
|
|
return ¬Found{"Device", fmt.Sprintf("uuid %s", string(idx))}
|
|
}
|
|
return UnmarshalDevice(v, &dev)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &dev, nil
|
|
}
|
|
|
|
func isNotFound(err error) bool {
|
|
if _, ok := err.(*notFound); ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (db *DB) pollCheckin(pubsubSvc pubsub.PublishSubscriber) error {
|
|
authenticateEvents, err := pubsubSvc.Subscribe(context.TODO(), "devices", checkin.AuthenticateTopic)
|
|
if err != nil {
|
|
return errors.Wrapf(err,
|
|
"subscribing devices to %s topic", checkin.AuthenticateTopic)
|
|
}
|
|
tokenUpdateEvents, err := pubsubSvc.Subscribe(context.TODO(), "devices", checkin.TokenUpdateTopic)
|
|
if err != nil {
|
|
return errors.Wrapf(err,
|
|
"subscribing devices to %s topic", checkin.TokenUpdateTopic)
|
|
}
|
|
checkoutEvents, err := pubsubSvc.Subscribe(context.TODO(), "devices", checkin.CheckoutTopic)
|
|
if err != nil {
|
|
return errors.Wrapf(err,
|
|
"subscribing devices to %s topic", checkin.CheckoutTopic)
|
|
}
|
|
depSyncEvents, err := pubsubSvc.Subscribe(context.TODO(), "devices", depsync.SyncTopic)
|
|
if err != nil {
|
|
return errors.Wrapf(err,
|
|
"subscribing devices to %s topic", depsync.SyncTopic)
|
|
}
|
|
connectEvents, err := pubsubSvc.Subscribe(context.TODO(), "devices", connect.ConnectTopic)
|
|
if err != nil {
|
|
return errors.Wrapf(err,
|
|
"subscribing devices to %s topic", connect.ConnectTopic)
|
|
}
|
|
go func() {
|
|
for {
|
|
select {
|
|
case event := <-authenticateEvents:
|
|
var ev checkin.Event
|
|
if err := checkin.UnmarshalEvent(event.Message, &ev); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
newDevice := new(Device)
|
|
bySerial, err := db.DeviceBySerial(ev.Command.SerialNumber)
|
|
if err == nil && bySerial != nil { // must be a DEP device
|
|
newDevice = bySerial
|
|
}
|
|
if err != nil && !isNotFound(err) {
|
|
fmt.Println(err) // some other issue is going on
|
|
continue
|
|
}
|
|
_, err = db.DeviceByUDID(ev.Command.UDID)
|
|
if err != nil && isNotFound(err) { // never checked in
|
|
fmt.Printf("checking in new device %s\n", ev.Command.SerialNumber)
|
|
} else if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
} else if err == nil {
|
|
fmt.Printf("re-enrolling device %s\n", ev.Command.SerialNumber)
|
|
newDevice.Enrolled = false
|
|
}
|
|
|
|
// only create new UUID on initial enrollment.
|
|
if newDevice.UUID == "" {
|
|
newDevice.UUID = uuid.NewV4().String()
|
|
}
|
|
newDevice.UDID = ev.Command.UDID
|
|
newDevice.OSVersion = ev.Command.OSVersion
|
|
newDevice.BuildVersion = ev.Command.BuildVersion
|
|
newDevice.ProductName = ev.Command.ProductName
|
|
newDevice.SerialNumber = ev.Command.SerialNumber
|
|
newDevice.IMEI = ev.Command.IMEI
|
|
newDevice.MEID = ev.Command.MEID
|
|
newDevice.DeviceName = ev.Command.DeviceName
|
|
newDevice.Model = ev.Command.Model
|
|
newDevice.ModelName = ev.Command.ModelName
|
|
newDevice.LastCheckin = time.Now()
|
|
// Challenge: ev.Command.Challenge, // FIXME: @groob why is this commented out?
|
|
|
|
if err := db.Save(newDevice); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
case event := <-tokenUpdateEvents:
|
|
var ev checkin.Event
|
|
if err := checkin.UnmarshalEvent(event.Message, &ev); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
if ev.Command.UserID != "" {
|
|
continue
|
|
}
|
|
dev, err := db.DeviceByUDID(ev.Command.UDID)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
dev.Token = ev.Command.Token.String()
|
|
dev.PushMagic = ev.Command.PushMagic
|
|
dev.UnlockToken = ev.Command.UnlockToken.String()
|
|
dev.AwaitingConfiguration = ev.Command.AwaitingConfiguration
|
|
dev.LastCheckin = time.Now()
|
|
var newlyEnrolled bool = false
|
|
if dev.Enrolled == false {
|
|
newlyEnrolled = true
|
|
dev.Enrolled = true
|
|
}
|
|
if err := db.Save(dev); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
if newlyEnrolled {
|
|
fmt.Printf("device %s enrolled\n", ev.Command.UDID)
|
|
err := pubsubSvc.Publish(context.TODO(), DeviceEnrolledTopic, event.Message)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
case event := <-depSyncEvents:
|
|
var ev depsync.Event
|
|
if err := depsync.UnmarshalEvent(event.Message, &ev); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
fmt.Printf("got %d devices from DEP\n", len(ev.Devices))
|
|
for _, d := range ev.Devices {
|
|
newDevice := new(Device)
|
|
bySerial, err := db.DeviceBySerial(d.SerialNumber)
|
|
if err == nil && bySerial != nil { // must be a DEP device
|
|
fmt.Printf("existing device checked in from DEP: %s\n", d.SerialNumber)
|
|
newDevice = bySerial
|
|
}
|
|
if err != nil && !isNotFound(err) {
|
|
fmt.Println(err) // some other issue is going on
|
|
continue
|
|
}
|
|
if newDevice.UUID == "" { // previously unknown
|
|
newDevice.UUID = uuid.NewV4().String()
|
|
}
|
|
newDevice.SerialNumber = d.SerialNumber
|
|
newDevice.Model = d.Model
|
|
newDevice.Description = d.Description
|
|
newDevice.Color = d.Color
|
|
newDevice.AssetTag = d.AssetTag
|
|
newDevice.DEPProfileStatus = DEPProfileStatus(d.ProfileStatus)
|
|
newDevice.DEPProfileUUID = d.ProfileUUID
|
|
newDevice.DEPProfileAssignTime = d.ProfileAssignTime
|
|
newDevice.DEPProfileAssignedDate = d.DeviceAssignedDate
|
|
newDevice.DEPProfileAssignedBy = d.DeviceAssignedBy
|
|
// TODO: deal with sync fields OpType, OpDate
|
|
if err := db.Save(newDevice); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
}
|
|
case event := <-connectEvents:
|
|
var ev connect.Event
|
|
if err := connect.UnmarshalEvent(event.Message, &ev); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
dev, err := db.DeviceByUDID(ev.Response.UDID)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
dev.LastCheckin = time.Now()
|
|
if err := db.Save(dev); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
case event := <-checkoutEvents:
|
|
var ev checkin.Event
|
|
if err := checkin.UnmarshalEvent(event.Message, &ev); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
dev, err := db.DeviceByUDID(ev.Command.UDID)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
dev.Enrolled = false
|
|
dev.LastCheckin = time.Now()
|
|
if err := db.Save(dev); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|