mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-13 22:15:42 +08:00
Flesh out implementation of acknowledging InstalledApplicationList response.
Additional tags on Application for database. Applications datastore supports select with arbitrary where clauses Changed the signatures of all the ack handlers in connect service.
This commit is contained in:
@@ -1,31 +1,125 @@
|
||||
package applications
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/micromdm/mdm"
|
||||
// "github.com/micromdm/micromdm/device"
|
||||
"github.com/micromdm/micromdm/device"
|
||||
)
|
||||
|
||||
//
|
||||
//func (svc service) ackInstalledApplicationList(req mdm.Response) error {
|
||||
// apps := req.InstalledApplicationList
|
||||
// devices, err := svc.devices.Devices(
|
||||
// device.UDID{UDID: req.UDID},
|
||||
// )
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// if len(devices) > 1 || len(devices) == 0 {
|
||||
// return errors.New("expected a single query result for device, got more or less than one.")
|
||||
// }
|
||||
// device := devices[0]
|
||||
//
|
||||
// for _, app := range apps {
|
||||
//
|
||||
// }
|
||||
//}
|
||||
func AppListPredicate(response mdm.Response) bool {
|
||||
fmt.Println("InstalledApplicationList Predicate")
|
||||
|
||||
if response.RequestType == "InstalledApplicationList" {
|
||||
return true
|
||||
}
|
||||
|
||||
if response.InstalledApplicationList != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func any(list []Application, predicate func(Application) bool) bool {
|
||||
for _, v := range list {
|
||||
if predicate(v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func AppListResponse(response mdm.Response, datastores map[string]interface{}) error {
|
||||
store, found := datastores["applications"]
|
||||
if !found {
|
||||
return errors.New("Do not have access to datastore for saving application information")
|
||||
}
|
||||
|
||||
appsStore, ok := store.(Datastore)
|
||||
if !ok {
|
||||
return errors.New("could not acknowledge installed application list because the given datastore isnt an application datastore.")
|
||||
}
|
||||
|
||||
dstore, found := datastores["devices"]
|
||||
if !found {
|
||||
return errors.New("Do not have access to datastore for retrieving device information")
|
||||
}
|
||||
|
||||
deviceStore, ok := dstore.(device.Datastore)
|
||||
if !ok {
|
||||
return errors.New("could not acknowledge installed application list because the given device datastore isnt a device datastore.")
|
||||
}
|
||||
|
||||
device, err := deviceStore.GetDeviceByUDID(response.UDID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deviceApps, err := appsStore.GetApplicationsByDeviceUUID(device.UUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var uuids []string
|
||||
for _, app := range response.InstalledApplicationList {
|
||||
var uuid string
|
||||
|
||||
existingApps, err := appsStore.Applications(Name{app.Name}, Version{app.Version})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(existingApps) > 0 {
|
||||
existingApp := existingApps[0]
|
||||
uuid = existingApp.UUID
|
||||
uuids = append(uuids, existingApp.UUID)
|
||||
} else {
|
||||
dbApp := &Application{Name: app.Name}
|
||||
|
||||
identifier := sql.NullString{}
|
||||
identifier.Scan(app.Identifier)
|
||||
dbApp.Identifier = identifier
|
||||
|
||||
bundleSize := sql.NullInt64{}
|
||||
bundleSize.Scan(app.BundleSize)
|
||||
dbApp.BundleSize = bundleSize
|
||||
|
||||
shortVersion := sql.NullString{}
|
||||
shortVersion.Scan(app.ShortVersion)
|
||||
dbApp.ShortVersion = shortVersion
|
||||
|
||||
version := sql.NullString{}
|
||||
version.Scan(app.Version)
|
||||
dbApp.Version = version
|
||||
|
||||
dynamicSize := sql.NullInt64{}
|
||||
dynamicSize.Scan(app.DynamicSize)
|
||||
dbApp.DynamicSize = dynamicSize
|
||||
|
||||
isValidated := sql.NullBool{}
|
||||
isValidated.Scan(app.IsValidated)
|
||||
dbApp.IsValidated = isValidated
|
||||
|
||||
uuid, err := appsStore.New(dbApp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uuids = append(uuids, uuid)
|
||||
}
|
||||
|
||||
if !any(deviceApps, app) {
|
||||
// App installed on device but not recorded
|
||||
|
||||
//deviceApp := &DeviceApplication{
|
||||
// DeviceUUID: device.UUID,
|
||||
// ApplicationUUID: app.UUID,
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
func AcknowledgeInstalledApplicationListResponse(response mdm.Response, datastores map[string]interface{}) error {
|
||||
fmt.Println("InstalledApplicationListResponseHandler TODO")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,14 +3,20 @@ package applications
|
||||
import "database/sql"
|
||||
|
||||
type Application struct {
|
||||
Identifier sql.NullString `plist:",omitempty" json:"identifier,omitempty"`
|
||||
Version sql.NullString `plist:",omitempty" json:"version,omitempty"`
|
||||
ShortVersion sql.NullString `plist:",omitempty" json:"short_version,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
BundleSize sql.NullInt64 `plist:",omitempty" json:"bundle_size,omitempty"`
|
||||
UUID string `plist:",omitempty" json:"uuid,omitempty" db:"application_uuid"`
|
||||
Identifier sql.NullString `plist:",omitempty" json:"identifier,omitempty" db:"identifier"`
|
||||
Version sql.NullString `plist:",omitempty" json:"version,omitempty" db:"version"`
|
||||
ShortVersion sql.NullString `plist:",omitempty" json:"short_version,omitempty" db:"short_version"`
|
||||
Name string `json:"name,omitempty" db:"name"`
|
||||
BundleSize sql.NullInt64 `plist:",omitempty" json:"bundle_size,omitempty" db:"bundle_size"`
|
||||
|
||||
// The size of the app's document, library, and other folders, in bytes.
|
||||
DynamicSize sql.NullInt64 `plist:",omitempty" json:"dynamic_size,omitempty"`
|
||||
DynamicSize sql.NullInt64 `plist:",omitempty" json:"dynamic_size,omitempty" db:"dynamic_size"`
|
||||
|
||||
IsValidated sql.NullBool `plist:",omitempty" json:"is_validated,omitempty"`
|
||||
IsValidated sql.NullBool `plist:",omitempty" json:"is_validated,omitempty" db:"is_validated"`
|
||||
}
|
||||
|
||||
type DeviceApplication struct {
|
||||
DeviceUUID string `json:"device_uuid" db:"device_uuid"`
|
||||
ApplicationUUID string `json:"application_uuid" db:"application_uuid"`
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq" // postgres driver
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Datastore manages devices in a database
|
||||
type Datastore interface {
|
||||
New(a *Application) (string, error)
|
||||
Applications(params ...interface{}) ([]Application, error)
|
||||
GetApplicationsByDeviceUUID(deviceUUID string) (*[]Application, error)
|
||||
}
|
||||
|
||||
@@ -44,8 +47,77 @@ func NewDB(driver, conn string, logger kitlog.Logger) (Datastore, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (store pgStore) New(src string, a *Application) (string, error) {
|
||||
return "", nil
|
||||
// UUID is a filter that can be added as a parameter to narrow down the list of returned results
|
||||
type UUID struct {
|
||||
UUID string
|
||||
}
|
||||
|
||||
func (p UUID) where() string {
|
||||
return fmt.Sprintf("application_uuid = '%s'", p.UUID)
|
||||
}
|
||||
|
||||
type Name struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (p Name) where() string {
|
||||
return fmt.Sprintf("name = '%s'", p.Name)
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func (p Version) where() string {
|
||||
return fmt.Sprintf("version = '%s'", p.Version)
|
||||
}
|
||||
|
||||
func (store pgStore) New(a *Application) (string, error) {
|
||||
err := store.QueryRow(
|
||||
`INSERT INTO applications (
|
||||
name,
|
||||
identifier,
|
||||
short_version,
|
||||
version,
|
||||
bundle_size,
|
||||
dynamic_size,
|
||||
is_validated
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (name, version) DO UPDATE SET
|
||||
identifier=$2,
|
||||
short_version=$3,
|
||||
bundle_size=$5,
|
||||
dynamic_size=$6,
|
||||
is_validated=$7
|
||||
RETURNING application_uuid;`,
|
||||
a.Name,
|
||||
a.Identifier,
|
||||
a.ShortVersion,
|
||||
a.Version,
|
||||
a.BundleSize,
|
||||
a.DynamicSize,
|
||||
a.IsValidated,
|
||||
).Scan(&a.UUID)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return a.UUID, nil
|
||||
}
|
||||
|
||||
func (store pgStore) Applications(params ...interface{}) ([]Application, error) {
|
||||
stmt := `SELECT * FROM applications`
|
||||
stmt = addWhereFilters(stmt, "OR", params...)
|
||||
|
||||
var apps []Application
|
||||
|
||||
err := store.Select(&apps, stmt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pgStore Applications")
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func (store pgStore) GetApplicationsByDeviceUUID(deviceUUID string) (*[]Application, error) {
|
||||
@@ -62,3 +134,24 @@ func (store pgStore) GetApplicationsByDeviceUUID(deviceUUID string) (*[]Applicat
|
||||
|
||||
return &apps, nil
|
||||
}
|
||||
|
||||
// whereer is for building args passed into a method which finds resources
|
||||
type whereer interface {
|
||||
where() string
|
||||
}
|
||||
|
||||
// add WHERE clause from params
|
||||
func addWhereFilters(stmt string, separator string, params ...interface{}) string {
|
||||
var where []string
|
||||
for _, param := range params {
|
||||
if f, ok := param.(whereer); ok {
|
||||
where = append(where, f.where())
|
||||
}
|
||||
}
|
||||
|
||||
if len(where) != 0 {
|
||||
whereFilter := strings.Join(where, " "+separator+" ")
|
||||
stmt = fmt.Sprintf("%s WHERE %s", stmt, whereFilter)
|
||||
}
|
||||
return stmt
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package connect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/micromdm/mdm"
|
||||
"github.com/micromdm/micromdm/command"
|
||||
"github.com/micromdm/micromdm/device"
|
||||
@@ -14,9 +15,9 @@ type Service interface {
|
||||
NextCommand(ctx context.Context, req mdm.Response) ([]byte, int, error)
|
||||
FailCommand(ctx context.Context, req mdm.Response) (int, error)
|
||||
|
||||
RegisterAckHandler(requestType string, handler func(req mdm.Response, datastores map[string]interface{}) error, datastores map[string]interface{})
|
||||
FindAckHandler(requestType string) (func(req mdm.Response) error, bool)
|
||||
ExecAckHandler(requestType string, req mdm.Response) error
|
||||
RegisterAckHandler(predicate func(req mdm.Response) bool, handler func(req mdm.Response, datastores map[string]interface{}) error, datastores map[string]interface{})
|
||||
FindAckHandler(req mdm.Response) (func(req mdm.Response) error, bool)
|
||||
ExecAckHandler(req mdm.Response) error
|
||||
}
|
||||
|
||||
// NewService creates a mdm service
|
||||
@@ -24,12 +25,13 @@ func NewService(devices device.Datastore, cs command.Service) Service {
|
||||
return &service{
|
||||
commands: cs,
|
||||
devices: devices,
|
||||
handlers: []ackHandler{},
|
||||
}
|
||||
}
|
||||
|
||||
type ackHandler struct {
|
||||
requestType string
|
||||
handler func(req mdm.Response) error
|
||||
predicate func(req mdm.Response) bool
|
||||
handler func(req mdm.Response) error
|
||||
}
|
||||
|
||||
type service struct {
|
||||
@@ -39,15 +41,10 @@ type service struct {
|
||||
}
|
||||
|
||||
func (svc service) Acknowledge(ctx context.Context, req mdm.Response) (int, error) {
|
||||
|
||||
err := svc.ExecAckHandler(req.RequestType, req)
|
||||
|
||||
//// Need to handle the absence of RequestType in IOS8 devices
|
||||
//if req.QueryResponses.UDID != "" {
|
||||
// if err := svc.ackQueryResponses(req); err != nil {
|
||||
// return 0, err
|
||||
// }
|
||||
//}
|
||||
err := svc.ExecAckHandler(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
total, err := svc.commands.DeleteCommand(req.UDID, req.CommandUUID)
|
||||
if err != nil {
|
||||
@@ -90,18 +87,20 @@ func (svc service) checkRequeue(deviceUDID string) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Register a handler function for a given RequestType, include datastore dependencies as a map.
|
||||
func (svc service) RegisterAckHandler(requestType string, handler func(req mdm.Response, datastores map[string]interface{}) error, datastores map[string]interface{}) {
|
||||
// Register a handler function for a given request, include datastore dependencies as a map.
|
||||
func (svc *service) RegisterAckHandler(predicate func(req mdm.Response) bool, handler func(req mdm.Response, datastores map[string]interface{}) error, datastores map[string]interface{}) {
|
||||
datastoreInjectedHandler := func(req mdm.Response) error {
|
||||
return handler(req, datastores)
|
||||
}
|
||||
svc.handlers = append(svc.handlers, ackHandler{requestType, datastoreInjectedHandler})
|
||||
newHandler := ackHandler{predicate: predicate, handler: datastoreInjectedHandler}
|
||||
svc.handlers = append(svc.handlers, newHandler)
|
||||
}
|
||||
|
||||
// Find a handler function which is registered to deal with the RequestType
|
||||
func (svc service) FindAckHandler(requestType string) (func(req mdm.Response) error, bool) {
|
||||
for _, h := range svc.handlers {
|
||||
if h.requestType == requestType {
|
||||
func (svc service) FindAckHandler(req mdm.Response) (func(req mdm.Response) error, bool) {
|
||||
for i, h := range svc.handlers {
|
||||
fmt.Println(i)
|
||||
if h.predicate(req) {
|
||||
return h.handler, true
|
||||
}
|
||||
}
|
||||
@@ -110,10 +109,10 @@ func (svc service) FindAckHandler(requestType string) (func(req mdm.Response) er
|
||||
}
|
||||
|
||||
// Execute any registered handler function which matches the given RequestType
|
||||
func (svc service) ExecAckHandler(requestType string, req mdm.Response) error {
|
||||
handler, found := svc.FindAckHandler(requestType)
|
||||
func (svc service) ExecAckHandler(req mdm.Response) error {
|
||||
handler, found := svc.FindAckHandler(req)
|
||||
if !found {
|
||||
return errors.Errorf("There is no registered handler for the response type: %s", requestType)
|
||||
return errors.New("There is no registered handler for the response.")
|
||||
}
|
||||
|
||||
return handler(req)
|
||||
|
||||
@@ -7,8 +7,21 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func AckQueryResponsesPredicate(req mdm.Response) bool {
|
||||
if req.RequestType == "DeviceInformation" {
|
||||
return true
|
||||
}
|
||||
|
||||
//// Need to handle the absence of RequestType in IOS8 devices
|
||||
if req.QueryResponses.UDID != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Acknowledge Queries sent with DeviceInformation command
|
||||
func AcknowledgeDeviceInformationResponse(req mdm.Response, datastores map[string]interface{}) error {
|
||||
func AckQueryResponsesResponse(req mdm.Response, datastores map[string]interface{}) error {
|
||||
store, found := datastores["devices"]
|
||||
if !found {
|
||||
return errors.New("Do not have access to datastore for saving device information")
|
||||
|
||||
8
main.go
8
main.go
@@ -204,14 +204,14 @@ func main() {
|
||||
connectSvc := connect.NewService(deviceDB, commandSvc)
|
||||
|
||||
connectSvc.RegisterAckHandler(
|
||||
"InstalledApplicationList",
|
||||
applications.AcknowledgeInstalledApplicationListResponse,
|
||||
applications.AppListPredicate,
|
||||
applications.AppListResponse,
|
||||
map[string]interface{}{"applications": appsDB},
|
||||
)
|
||||
|
||||
connectSvc.RegisterAckHandler(
|
||||
"DeviceInformation",
|
||||
device.AcknowledgeDeviceInformationResponse,
|
||||
device.AckQueryResponsesPredicate,
|
||||
device.AckQueryResponsesResponse,
|
||||
map[string]interface{}{"devices": deviceDB},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user