add checkout middleware (#342)

Implement a connect service middleware which allows an operator to forcefully un-enroll a device. 

Documentation: https://github.com/micromdm/micromdm/wiki/Terminating-a-Management-Relationship-with-a-device-block
This commit is contained in:
Victor Vrantchan
2017-11-30 23:17:51 -05:00
committed by GitHub
parent a4bcc70a93
commit ce6e18f18d
21 changed files with 564 additions and 33 deletions

View File

@@ -6,6 +6,8 @@ import (
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
@@ -60,6 +62,8 @@ func (cmd *applyCommand) Run(args []string) error {
run = cmd.applyProfile
case "app":
run = cmd.applyApp
case "block":
run = cmd.applyBlock
case "users":
run = cmd.applyUser
default:
@@ -81,6 +85,7 @@ Valid resource types:
* dep-tokens
* dep-profiles
* app
* block
Examples:
# Apply a Blueprint.
@@ -189,6 +194,40 @@ func (cmd *applyCommand) applyDEPTokens(args []string) error {
return nil
}
func (cmd *applyCommand) applyBlock(args []string) error {
flagset := flag.NewFlagSet("block", flag.ExitOnError)
var (
flUDID = flagset.String("udid", "", "UDID of a device to block.")
)
flagset.Usage = usageFor(flagset, "mdmctl apply block [flags]")
if err := flagset.Parse(args); err != nil {
return err
}
if *flUDID == "" {
flagset.Usage()
return errors.New("bad input: must provide a device UDID to block.")
}
if err := cmd.applysvc.BlockDevice(context.Background(), *flUDID); err != nil {
return err
}
// trigger a push
u, err := url.Parse(cmd.config.ServerURL)
if err != nil {
fmt.Println(err)
return nil
}
u.Path = "/push/" + url.QueryEscape(*flUDID)
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
fmt.Println(err)
return nil
}
req.SetBasicAuth("micromdm", cmd.config.APIToken)
http.DefaultClient.Do(req)
return nil
}
func (cmd *applyCommand) applyProfile(args []string) error {
flagset := flag.NewFlagSet("profiles", flag.ExitOnError)
var (

View File

@@ -48,6 +48,8 @@ func (cmd *removeCommand) Run(args []string) error {
run = cmd.removeBlueprints
case "profiles":
run = cmd.removeProfiles
case "block":
run = cmd.removeBlock
default:
cmd.Usage()
os.Exit(1)
@@ -63,7 +65,9 @@ Display one or many resources.
Valid resource types:
* blueprints
* profiles`
* profiles
* block
`
fmt.Println(getUsage)
return nil

View File

@@ -0,0 +1,34 @@
package main
import (
"context"
"flag"
"fmt"
"github.com/pkg/errors"
)
func (cmd *removeCommand) removeBlock(args []string) error {
flagset := flag.NewFlagSet("unblock", flag.ExitOnError)
var (
flUDID = flagset.String("udid", "", "UDID of device to unblock")
)
flagset.Usage = usageFor(flagset, "mdmctl remove block [flags]")
if err := flagset.Parse(args); err != nil {
return err
}
if *flUDID == "" {
flagset.Usage()
return errors.New("bad input: must provide a device UDID to unblock.")
}
ctx := context.Background()
if err := cmd.remove.UnblockDevice(ctx, *flUDID); err != nil {
return err
}
fmt.Println("success")
return nil
}

View File

@@ -57,6 +57,7 @@ import (
"github.com/micromdm/micromdm/platform/pubsub"
"github.com/micromdm/micromdm/platform/pubsub/inmem"
"github.com/micromdm/micromdm/platform/queue"
block "github.com/micromdm/micromdm/platform/remove"
"github.com/micromdm/micromdm/platform/user"
"github.com/micromdm/micromdm/workflow/webhook"
)
@@ -142,8 +143,10 @@ func serve(args []string) error {
// no less secure and prevents a useless dialog from showing.
SCEPChallenge: "micromdm",
}
sm.setupPubSub()
sm.setupBolt()
sm.setupRemoveService()
sm.setupConfigStore()
sm.loadPushCerts()
sm.setupSCEP(logger)
@@ -157,6 +160,11 @@ func serve(args []string) error {
stdlog.Fatal(sm.err)
}
removeService, err := block.NewService(sm.removeDB)
if err != nil {
stdlog.Fatal(err)
}
devDB, err := device.NewDB(sm.db, sm.pubclient)
if err != nil {
stdlog.Fatal(err)
@@ -298,12 +306,13 @@ func serve(args []string) error {
var applysvc apply.Service
{
l := &apply.ApplyService{
DEPClient: dc,
Blueprints: bpDB,
Tokens: tokenDB,
Profiles: sm.profileDB,
Apps: appDB,
Users: userDB,
DEPClient: dc,
Blueprints: bpDB,
Tokens: tokenDB,
Profiles: sm.profileDB,
Apps: appDB,
Users: userDB,
RemoveService: removeService,
}
applysvc = l
if err := l.WatchTokenUpdates(sm.pubclient); err != nil {
@@ -343,13 +352,14 @@ func serve(args []string) error {
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
AppUploadEndpoint: appUploadEndpoint,
ApplyUserEndpoint: applyUserEndpoint,
BlockDeviceEndpoint: apply.MakeBlockDeviceEndpoint(applysvc),
}
applyAPIHandlers := apply.MakeHTTPHandlers(ctx, applyEndpoints, connectOpts...)
listAPIHandlers := list.MakeHTTPHandlers(ctx, listEndpoints, connectOpts...)
rmsvc := &remove.RemoveService{Blueprints: bpDB, Profiles: sm.profileDB}
rmsvc := &remove.RemoveService{Blueprints: bpDB, Profiles: sm.profileDB, RemoveService: removeService}
removeAPIHandlers := remove.MakeHTTPHandlers(ctx, remove.MakeEndpoints(rmsvc), connectOpts...)
connectHandlers := connect.MakeHTTPHandlers(ctx, connectEndpoints, connectOpts...)
@@ -373,6 +383,8 @@ func serve(args []string) error {
r.Handle("/push/{udid}", apiAuthMiddleware(*flAPIKey, pushHandlers.PushHandler))
r.Handle("/v1/commands", apiAuthMiddleware(*flAPIKey, commandHandlers.NewCommandHandler)).Methods("POST")
r.Handle("/v1/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListDevicesHandler)).Methods("GET")
r.Handle("/v1/devices/{udid}/block", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.BlockDeviceHandler)).Methods("POST")
r.Handle("/v1/devices/{udid}/unblock", apiAuthMiddleware(*flAPIKey, removeAPIHandlers.UnblockDeviceHandler)).Methods("POST")
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPTokensHandler)).Methods("GET")
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DEPTokensHandler)).Methods("PUT")
r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetBlueprintsHandler)).Methods("GET")
@@ -486,6 +498,7 @@ type server struct {
scepDepot *boltdepot.Depot
profileDB *profile.DB
configDB *config.DB
removeDB *block.DB
CommandWebhookURL string
// TODO: refactor enroll service and remove the need to reference
@@ -496,7 +509,7 @@ type server struct {
PushService *push.Service // bufford push
pushService apns.Service
checkinService checkin.Service
connectService connect.ConnectService
connectService connect.Service
enrollService enroll.Service
scepService scep.Service
commandService command.Service
@@ -550,6 +563,18 @@ func (c *server) startWebhooks() {
}
}
func (c *server) setupRemoveService() {
if c.err != nil {
return
}
removeDB, err := block.NewDB(c.db)
if err != nil {
c.err = err
return
}
c.removeDB = removeDB
}
func (c *server) setupCommandQueue(logger log.Logger) {
if c.err != nil {
return
@@ -560,18 +585,16 @@ func (c *server) setupCommandQueue(logger log.Logger) {
return
}
var connectService connect.ConnectService
var connectService connect.Service
{
svc, err := connect.New(q, c.pubclient)
if err != nil {
c.err = err
return
}
svc = connect.NewLoggingService(
svc,
log.With(level.Info(logger), "component", "connect"),
)
connectService = svc
connectService = connect.LoggingMiddleware(log.With(level.Info(logger), "component", "connect"))(svc)
connectService = block.RemoveMiddleware(c.removeDB)(connectService)
}
c.connectService = connectService
}

View File

@@ -12,16 +12,16 @@ import (
const ConnectTopic = "mdm.Connect"
// The ConnectService accepts responses sent to an MDM server by an enrolled
// The Service accepts responses sent to an MDM server by an enrolled
// device.
type ConnectService interface {
type Service interface {
// Acknowledge acknowledges a response sent by a device and returns
// the next payload if one is available.
Acknowledge(ctx context.Context, req MDMConnectRequest) (payload []byte, err error)
}
type connectSvc struct {
type ConnectService struct {
queue Queue
pub pubsub.Publisher
}
@@ -30,14 +30,14 @@ type Queue interface {
Next(context.Context, mdm.Response) (*queue.Command, error)
}
func New(queue Queue, pub pubsub.Publisher) (ConnectService, error) {
return &connectSvc{
func New(queue Queue, pub pubsub.Publisher) (*ConnectService, error) {
return &ConnectService{
queue: queue,
pub: pub,
}, nil
}
func (svc *connectSvc) Acknowledge(ctx context.Context, req MDMConnectRequest) (payload []byte, err error) {
func (svc *ConnectService) Acknowledge(ctx context.Context, req MDMConnectRequest) (payload []byte, err error) {
event := NewEvent(req)
msg, err := MarshalEvent(event)
if err != nil {

View File

@@ -23,7 +23,7 @@ type Endpoints struct {
ConnectEndpoint endpoint.Endpoint
}
func MakeConnectEndpoint(svc ConnectService) endpoint.Endpoint {
func MakeConnectEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(MDMConnectRequest)
payload, err := svc.Acknowledge(ctx, req)

View File

@@ -7,16 +7,20 @@ import (
"github.com/go-kit/kit/log"
)
type loggingMiddleware struct {
logger log.Logger
next ConnectService
type Middleware func(Service) Service
func LoggingMiddleware(logger log.Logger) Middleware {
return func(next Service) Service {
return &loggingMiddleware{
next: next,
logger: logger,
}
}
}
func NewLoggingService(svc ConnectService, logger log.Logger) loggingMiddleware {
return loggingMiddleware{
next: svc,
logger: logger,
}
type loggingMiddleware struct {
next Service
logger log.Logger
}
func (mw loggingMiddleware) Acknowledge(ctx context.Context, req MDMConnectRequest) (payload []byte, err error) {

View File

@@ -2,8 +2,8 @@ package connect
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
httptransport "github.com/go-kit/kit/transport/http"
@@ -68,6 +68,17 @@ func encodeResponse(ctx context.Context, w http.ResponseWriter, response interfa
// The EncodeError should be passed to the Go-Kit httptransport as the
// ServerErrorEncoder to encode error responses.
func EncodeError(ctx context.Context, err error, w http.ResponseWriter) {
fmt.Printf("connect error: %s\n", err)
type checkoutErr interface {
error
Checkout() bool
}
if e, ok := err.(checkoutErr); ok {
if e.Checkout() {
log.Printf("connect: forced checkout error: %s\n", err)
w.WriteHeader(http.StatusUnauthorized)
return
}
}
log.Printf("connect error: %s\n", err)
w.WriteHeader(http.StatusInternalServerError)
}

View File

@@ -80,6 +80,17 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
).Endpoint()
}
var blockDeviceEndpoint endpoint.Endpoint
{
blockDeviceEndpoint = httptransport.NewClient(
"POST",
copyURL(u, ""), // empty path, modified by the encodeRequest func
encodeRequestWithToken(token, encodeBlockDeviceRequest),
DecodeBlockDeviceResponse,
opts...,
).Endpoint()
}
return Endpoints{
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
ApplyDEPTokensEndpoint: applyDEPTokensEndpoint,
@@ -87,6 +98,7 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
DefineDEPProfileEndpoint: defineDEPProfileEndpoint,
AppUploadEndpoint: uploadAppEndpoint,
ApplyUserEndpoint: applyUserEndpoint,
BlockDeviceEndpoint: blockDeviceEndpoint,
}, nil
}

View File

@@ -19,6 +19,7 @@ type Endpoints struct {
DefineDEPProfileEndpoint endpoint.Endpoint
AppUploadEndpoint endpoint.Endpoint
ApplyUserEndpoint endpoint.Endpoint
BlockDeviceEndpoint endpoint.Endpoint
}
func (e Endpoints) ApplyUser(ctx context.Context, u user.User) (*user.User, error) {
@@ -33,6 +34,17 @@ func (e Endpoints) ApplyUser(ctx context.Context, u user.User) (*user.User, erro
return &usr, resp.(applyUserResponse).Err
}
func (e Endpoints) BlockDevice(ctx context.Context, udid string) error {
request := blockDeviceRequest{
UDID: udid,
}
resp, err := e.BlockDeviceEndpoint(ctx, request)
if err != nil {
return err
}
return resp.(blockDeviceResponse).Err
}
func (e Endpoints) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error {
request := appUploadRequest{
ManifestName: manifestName,
@@ -146,6 +158,16 @@ func MakeUploadAppEndpiont(svc Service) endpoint.Endpoint {
}
}
func MakeBlockDeviceEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(blockDeviceRequest)
err = svc.BlockDevice(ctx, req.UDID)
return &blockDeviceResponse{
Err: err,
}, nil
}
}
type appUploadRequest struct {
ManifestName string
ManifestFile io.Reader
@@ -208,3 +230,13 @@ type applyUserResponse struct {
}
func (r applyUserResponse) error() error { return r.Err }
type blockDeviceRequest struct {
UDID string
}
type blockDeviceResponse struct {
Err error `json:"err,omitempty"`
}
func (r blockDeviceResponse) error() error { return r.Err }

View File

@@ -21,6 +21,7 @@ import (
"github.com/micromdm/micromdm/platform/deptoken"
"github.com/micromdm/micromdm/platform/profile"
"github.com/micromdm/micromdm/platform/pubsub"
"github.com/micromdm/micromdm/platform/remove"
"github.com/micromdm/micromdm/platform/user"
)
@@ -31,6 +32,7 @@ type Service interface {
UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error
ApplyUser(ctx context.Context, u user.User) (*user.User, error)
DEPService
BlockDevice(ctx context.Context, udid string) error
}
type ApplyService struct {
@@ -42,6 +44,7 @@ type ApplyService struct {
Tokens *deptoken.DB
Apps appstore.AppStore
Users *user.DB
*remove.RemoveService
}
func (svc *ApplyService) ApplyUser(ctx context.Context, u user.User) (*user.User, error) {

View File

@@ -8,8 +8,10 @@ import (
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/gorilla/mux"
"github.com/pkg/errors"
)
@@ -20,6 +22,7 @@ type HTTPHandlers struct {
DefineDEPProfileHandler http.Handler
AppUploadHandler http.Handler
ApplyUserhandler http.Handler
BlockDeviceHandler http.Handler
}
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
@@ -60,10 +63,28 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
encodeResponse,
opts...,
),
BlockDeviceHandler: httptransport.NewServer(
endpoints.BlockDeviceEndpoint,
decodeBlockDeviceRequest,
encodeResponse,
opts...,
),
}
return h
}
func decodeBlockDeviceRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var errBadRoute = errors.New("bad route")
var req blockDeviceRequest
vars := mux.Vars(r)
udid, ok := vars["udid"]
if !ok {
return 0, errBadRoute
}
req.UDID = udid
return req, nil
}
func decodeDEPTokensRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var req depTokensRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -208,6 +229,13 @@ func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interf
return nil
}
func encodeBlockDeviceRequest(_ context.Context, r *http.Request, request interface{}) error {
req := request.(blockDeviceRequest)
udid := url.QueryEscape(req.UDID)
r.Method, r.URL.Path = "POST", "/v1/devices/"+udid+"/block"
return nil
}
func DecodeBlueprintResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
@@ -261,3 +289,12 @@ func DecodeApplyUserResponse(_ context.Context, r *http.Response) (interface{},
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeBlockDeviceResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp blockDeviceResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}

View File

@@ -37,9 +37,21 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra
).Endpoint()
}
var unblockDeviceEndpoint endpoint.Endpoint
{
unblockDeviceEndpoint = httptransport.NewClient(
"POST",
copyURL(u, ""), //modified by encodeRequestFunc
encodeRequestWithToken(token, encodeUnblockDeviceRequest),
DecodeUnblockDeviceResponse,
opts...,
).Endpoint()
}
return Endpoints{
RemoveBlueprintsEndpoint: removeBlueprintsEndpoint,
RemoveProfilesEndpoint: removeProfilesEndpoint,
UnblockDeviceEndpoint: unblockDeviceEndpoint,
}, nil
}

View File

@@ -9,16 +9,27 @@ import (
type Endpoints struct {
RemoveBlueprintsEndpoint endpoint.Endpoint
RemoveProfilesEndpoint endpoint.Endpoint
UnblockDeviceEndpoint endpoint.Endpoint
}
func MakeEndpoints(svc Service) Endpoints {
e := Endpoints{
RemoveBlueprintsEndpoint: MakeRemoveBlueprintsEndpoint(svc),
RemoveProfilesEndpoint: MakeRemoveProfilesEndpoint(svc),
UnblockDeviceEndpoint: MakeUnblockDeviceEndpoint(svc),
}
return e
}
func (e Endpoints) UnblockDevice(ctx context.Context, udid string) error {
request := unblockDeviceRequest{UDID: udid}
resp, err := e.UnblockDeviceEndpoint(ctx, request)
if err != nil {
return err
}
return resp.(unblockDeviceResponse).Err
}
func (e Endpoints) RemoveBlueprints(ctx context.Context, names []string) error {
request := blueprintRequest{Names: names}
resp, err := e.RemoveBlueprintsEndpoint(ctx, request)
@@ -57,6 +68,26 @@ func MakeRemoveProfilesEndpoint(svc Service) endpoint.Endpoint {
}
}
func MakeUnblockDeviceEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(unblockDeviceRequest)
err = svc.UnblockDevice(ctx, req.UDID)
return unblockDeviceResponse{
Err: err,
}, nil
}
}
type unblockDeviceRequest struct {
UDID string
}
type unblockDeviceResponse struct {
Err error `json:"err,omitempty"`
}
func (r unblockDeviceResponse) error() error { return r.Err }
type blueprintRequest struct {
Names []string `json:"names"`
}

View File

@@ -5,16 +5,19 @@ import (
"github.com/micromdm/micromdm/platform/blueprint"
"github.com/micromdm/micromdm/platform/profile"
"github.com/micromdm/micromdm/platform/remove"
)
type Service interface {
RemoveBlueprints(ctx context.Context, names []string) error
RemoveProfiles(ctx context.Context, ids []string) error
UnblockDevice(ctx context.Context, udid string) error
}
type RemoveService struct {
Blueprints *blueprint.DB
Profiles *profile.DB
*remove.RemoveService
}
func (svc *RemoveService) RemoveBlueprints(ctx context.Context, names []string) error {

View File

@@ -7,13 +7,16 @@ import (
"errors"
"io/ioutil"
"net/http"
"net/url"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/gorilla/mux"
)
type HTTPHandlers struct {
BlueprintHandler http.Handler
ProfileHandler http.Handler
BlueprintHandler http.Handler
ProfileHandler http.Handler
UnblockDeviceHandler http.Handler
}
func MakeHTTPHandlers(ctx context.Context, endpoint Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
@@ -30,6 +33,12 @@ func MakeHTTPHandlers(ctx context.Context, endpoint Endpoints, opts ...httptrans
encodeResponse,
opts...,
),
UnblockDeviceHandler: httptransport.NewServer(
endpoint.UnblockDeviceEndpoint,
decodeUnblockDeviceRequest,
encodeResponse,
opts...,
),
}
return h
}
@@ -50,6 +59,18 @@ func decodeProfileRequest(ctx context.Context, r *http.Request) (interface{}, er
return req, nil
}
func decodeUnblockDeviceRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var errBadRoute = errors.New("bad route")
var req unblockDeviceRequest
vars := mux.Vars(r)
udid, ok := vars["udid"]
if !ok {
return 0, errBadRoute
}
req.UDID = udid
return req, nil
}
type errorWrapper struct {
Error string `json:"error"`
}
@@ -95,6 +116,13 @@ func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interf
return nil
}
func encodeUnblockDeviceRequest(_ context.Context, r *http.Request, request interface{}) error {
req := request.(unblockDeviceRequest)
udid := url.QueryEscape(req.UDID)
r.Method, r.URL.Path = "POST", "/v1/devices/"+udid+"/unblock"
return nil
}
func DecodeBlueprintResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
@@ -112,3 +140,12 @@ func DecodeProfileResponse(_ context.Context, r *http.Response) (interface{}, er
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
func DecodeUnblockDeviceResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp unblockDeviceResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}

90
platform/remove/db.go Normal file
View File

@@ -0,0 +1,90 @@
package remove
import (
"fmt"
"github.com/boltdb/bolt"
"github.com/pkg/errors"
)
const RemoveBucket = "mdm.RemoveDevice"
type DB struct {
*bolt.DB
}
func NewDB(db *bolt.DB) (*DB, error) {
err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(RemoveBucket))
return err
})
if err != nil {
return nil, errors.Wrapf(err, "creating %s bucket", RemoveBucket)
}
datastore := &DB{
DB: db,
}
return datastore, nil
}
func (db *DB) DeviceByUDID(udid string) (*Device, error) {
var dev Device
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(RemoveBucket))
v := b.Get([]byte(udid))
if v == nil {
return &notFound{"Device", fmt.Sprintf("udid %s", udid)}
}
return UnmarshalDevice(v, &dev)
})
return &dev, errors.Wrap(err, "remove: get device by udid")
}
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(RemoveBucket))
if bkt == nil {
return fmt.Errorf("bucket %q not found!", RemoveBucket)
}
pb, err := MarshalDevice(dev)
if err != nil {
return errors.Wrap(err, "marshalling Device")
}
key := []byte(dev.UDID)
if err := bkt.Put(key, pb); err != nil {
return errors.Wrap(err, "put device to boltdb")
}
return tx.Commit()
}
func (db *DB) Delete(udid string) error {
err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(RemoveBucket))
v := b.Get([]byte(udid))
if v == nil {
return &notFound{"Device", fmt.Sprintf("udid %s", udid)}
}
return b.Delete([]byte(udid))
})
return errors.Wrapf(err, "delete device with udid %s", udid)
}
type notFound struct {
ResourceType string
Message string
}
func (e *notFound) Error() string {
return fmt.Sprintf("not found: %s %s", e.ResourceType, e.Message)
}
func isNotFound(err error) bool {
cause := errors.Cause(err)
if _, ok := cause.(*notFound); ok {
return true
}
return false
}

View File

@@ -0,0 +1,3 @@
package removeproto
//go:generate protoc --go_out=. remove.proto

View File

@@ -0,0 +1,59 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: remove.proto
/*
Package removeproto is a generated protocol buffer package.
It is generated from these files:
remove.proto
It has these top-level messages:
Device
*/
package removeproto
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Device struct {
Udid string `protobuf:"bytes,1,opt,name=udid" json:"udid,omitempty"`
}
func (m *Device) Reset() { *m = Device{} }
func (m *Device) String() string { return proto.CompactTextString(m) }
func (*Device) ProtoMessage() {}
func (*Device) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *Device) GetUdid() string {
if m != nil {
return m.Udid
}
return ""
}
func init() {
proto.RegisterType((*Device)(nil), "removeproto.Device")
}
func init() { proto.RegisterFile("remove.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 75 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x4a, 0xcd, 0xcd,
0x2f, 0x4b, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x86, 0xf0, 0xc0, 0x1c, 0x25, 0x19,
0x2e, 0x36, 0x97, 0xd4, 0xb2, 0xcc, 0xe4, 0x54, 0x21, 0x21, 0x2e, 0x96, 0xd2, 0x94, 0xcc, 0x14,
0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x30, 0x3b, 0x89, 0x0d, 0xac, 0xc8, 0x18, 0x10, 0x00,
0x00, 0xff, 0xff, 0xf6, 0xb9, 0x87, 0xa4, 0x41, 0x00, 0x00, 0x00,
}

View File

@@ -0,0 +1,7 @@
syntax = "proto3";
package removeproto;
message Device {
string udid = 1;
}

90
platform/remove/remove.go Normal file
View File

@@ -0,0 +1,90 @@
package remove
import (
"context"
"github.com/gogo/protobuf/proto"
"github.com/pkg/errors"
"github.com/micromdm/micromdm/mdm/connect"
"github.com/micromdm/micromdm/platform/remove/internal/removeproto"
)
type Service interface {
BlockDevice(ctx context.Context, udid string) error
UnblockDevice(ctx context.Context, udid string) error
}
type RemoveService struct {
db *DB
}
func NewService(db *DB) (*RemoveService, error) {
return &RemoveService{db: db}, nil
}
func (svc *RemoveService) BlockDevice(ctx context.Context, udid string) error {
return svc.db.Save(&Device{UDID: udid})
}
func (svc *RemoveService) UnblockDevice(ctx context.Context, udid string) error {
return svc.db.Delete(udid)
}
type Device struct {
UDID string `json:"udid"`
}
func MarshalDevice(dev *Device) ([]byte, error) {
protodev := removeproto.Device{
Udid: dev.UDID,
}
return proto.Marshal(&protodev)
}
func UnmarshalDevice(data []byte, dev *Device) error {
var pb removeproto.Device
if err := proto.Unmarshal(data, &pb); err != nil {
return errors.Wrap(err, "remove: unmarshal proto to device")
}
dev.UDID = pb.GetUdid()
return nil
}
func RemoveMiddleware(db *DB) connect.Middleware {
return func(next connect.Service) connect.Service {
return &removeMiddleware{
db: db,
next: next,
}
}
}
type removeMiddleware struct {
db *DB
next connect.Service
}
func (mw removeMiddleware) Acknowledge(ctx context.Context, req connect.MDMConnectRequest) ([]byte, error) {
udid := req.MDMResponse.UDID
_, err := mw.db.DeviceByUDID(udid)
if err != nil {
if !isNotFound(err) {
return nil, errors.Wrapf(err, "remove: get device by udid %s", udid)
}
}
if err == nil {
return nil, checkoutErr{}
}
return mw.next.Acknowledge(ctx, req)
}
type checkoutErr struct{}
func (checkoutErr) Error() string {
return "checkout forced by device block"
}
func (checkoutErr) Checkout() bool {
return true
}