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:
Mosen
2016-07-11 22:07:22 +10:00
parent 9f580a6ba2
commit f16b776cf8
6 changed files with 263 additions and 58 deletions

View File

@@ -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
}

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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")

View File

@@ -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},
)