diff --git a/cmd/mdmctl/apply.go b/cmd/mdmctl/apply.go index 02cbae22..2391d9d8 100644 --- a/cmd/mdmctl/apply.go +++ b/cmd/mdmctl/apply.go @@ -205,7 +205,7 @@ func (cmd *applyCommand) applyBlock(args []string) error { flagset.Usage() return errors.New("bad input: must provide a device UDID to block.") } - if err := cmd.applysvc.BlockDevice(context.Background(), *flUDID); err != nil { + if err := cmd.blocksvc.BlockDevice(context.Background(), *flUDID); err != nil { return err } diff --git a/cmd/mdmctl/mdmctl b/cmd/mdmctl/mdmctl deleted file mode 100755 index 8b02bfc4..00000000 Binary files a/cmd/mdmctl/mdmctl and /dev/null differ diff --git a/cmd/mdmctl/remove_block.go b/cmd/mdmctl/remove_block.go index 511afce3..24e51024 100644 --- a/cmd/mdmctl/remove_block.go +++ b/cmd/mdmctl/remove_block.go @@ -24,7 +24,7 @@ func (cmd *removeCommand) removeBlock(args []string) error { } ctx := context.Background() - if err := cmd.remove.UnblockDevice(ctx, *flUDID); err != nil { + if err := cmd.blocksvc.UnblockDevice(ctx, *flUDID); err != nil { return err } diff --git a/cmd/mdmctl/setup.go b/cmd/mdmctl/setup.go index e683882a..f02f749f 100644 --- a/cmd/mdmctl/setup.go +++ b/cmd/mdmctl/setup.go @@ -6,17 +6,17 @@ import ( "github.com/micromdm/micromdm/platform/api/server/apply" "github.com/micromdm/micromdm/platform/api/server/list" - "github.com/micromdm/micromdm/platform/api/server/remove" "github.com/micromdm/micromdm/platform/blueprint" "github.com/micromdm/micromdm/platform/profile" + "github.com/micromdm/micromdm/platform/remove" ) type remoteServices struct { profilesvc profile.Service blueprintsvc blueprint.Service + blocksvc remove.Service applysvc apply.Service list list.Service - remove remove.Service } func setupClient(logger log.Logger) (*remoteServices, error) { @@ -39,6 +39,13 @@ func setupClient(logger log.Logger) (*remoteServices, error) { return nil, err } + blocksvc, err := remove.NewHTTPClient( + cfg.ServerURL, cfg.APIToken, logger, + httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify))) + if err != nil { + return nil, err + } + applysvc, err := apply.NewClient( cfg.ServerURL, logger, cfg.APIToken, httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify))) @@ -53,18 +60,11 @@ func setupClient(logger log.Logger) (*remoteServices, error) { return nil, err } - rmsvc, err := remove.NewClient( - cfg.ServerURL, logger, cfg.APIToken, - httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify))) - if err != nil { - return nil, err - } - return &remoteServices{ profilesvc: profilesvc, blueprintsvc: blueprintsvc, + blocksvc: blocksvc, applysvc: applysvc, list: listsvc, - remove: rmsvc, }, nil } diff --git a/cmd/micromdm/serve.go b/cmd/micromdm/serve.go index 150aca4a..90766789 100644 --- a/cmd/micromdm/serve.go +++ b/cmd/micromdm/serve.go @@ -45,7 +45,6 @@ import ( "github.com/micromdm/micromdm/pkg/crypto" "github.com/micromdm/micromdm/platform/api/server/apply" "github.com/micromdm/micromdm/platform/api/server/list" - "github.com/micromdm/micromdm/platform/api/server/remove" "github.com/micromdm/micromdm/platform/apns" "github.com/micromdm/micromdm/platform/appstore" "github.com/micromdm/micromdm/platform/blueprint" @@ -60,6 +59,7 @@ import ( "github.com/micromdm/micromdm/platform/pubsub/inmem" "github.com/micromdm/micromdm/platform/queue" block "github.com/micromdm/micromdm/platform/remove" + blockbuiltin "github.com/micromdm/micromdm/platform/remove/builtin" "github.com/micromdm/micromdm/platform/user" "github.com/micromdm/micromdm/workflow/webhook" ) @@ -162,7 +162,7 @@ func serve(args []string) error { stdlog.Fatal(sm.err) } - removeService, err := block.NewService(sm.removeDB) + removeService, err := block.New(sm.removeDB) if err != nil { stdlog.Fatal(err) } @@ -285,6 +285,7 @@ func serve(args []string) error { blueprintEndpoints := blueprint.MakeServerEndpoints(blueprintsvc) + blockEndpoints := block.MakeServerEndpoints(removeService) var listsvc list.Service { l := &list.ListService{ @@ -318,11 +319,10 @@ func serve(args []string) error { var applysvc apply.Service { l := &apply.ApplyService{ - DEPClient: dc, - Tokens: tokenDB, - Apps: appDB, - Users: userDB, - RemoveService: removeService, + DEPClient: dc, + Tokens: tokenDB, + Apps: appDB, + Users: userDB, } applysvc = l if err := l.WatchTokenUpdates(sm.pubclient); err != nil { @@ -350,16 +350,12 @@ 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{RemoveService: removeService} - removeAPIHandlers := remove.MakeHTTPHandlers(ctx, remove.MakeEndpoints(rmsvc), connectOpts...) - connectHandlers := connect.MakeHTTPHandlers(ctx, connectEndpoints, connectOpts...) scepHandler := scep.ServiceHandler(ctx, sm.scepService, httpLogger) @@ -378,16 +374,17 @@ func serve(args []string) error { profilesHandler := profile.MakeHTTPHandler(profileEndpoints, logger) blueprintsHandler := blueprint.MakeHTTPHandler(blueprintEndpoints, logger) + blockhandler := block.MakeHTTPHandler(blockEndpoints, logger) // API commands. Only handled if the user provides an api key. if *flAPIKey != "" { r.Handle("/v1/profiles", apiAuthMiddleware(*flAPIKey, profilesHandler)) r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, blueprintsHandler)) + r.Handle("/v1/devices/{udid}/block", apiAuthMiddleware(*flAPIKey, blockhandler)) + r.Handle("/v1/devices/{udid}/unblock", apiAuthMiddleware(*flAPIKey, blockhandler)) 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/dep/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPDeviceDetailsHandler)).Methods("GET") @@ -495,7 +492,7 @@ type server struct { scepDepot *boltdepot.Depot profileDB profile.Store configDB *config.DB - removeDB *block.DB + removeDB block.Store CommandWebhookURL string // TODO: refactor enroll service and remove the need to reference @@ -564,7 +561,7 @@ func (c *server) setupRemoveService() { if c.err != nil { return } - removeDB, err := block.NewDB(c.db) + removeDB, err := blockbuiltin.NewDB(c.db) if err != nil { c.err = err return diff --git a/platform/api/server/apply/client.go b/platform/api/server/apply/client.go index 9e3657aa..e4eb6762 100644 --- a/platform/api/server/apply/client.go +++ b/platform/api/server/apply/client.go @@ -60,23 +60,11 @@ 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{ ApplyDEPTokensEndpoint: applyDEPTokensEndpoint, DefineDEPProfileEndpoint: defineDEPProfileEndpoint, AppUploadEndpoint: uploadAppEndpoint, ApplyUserEndpoint: applyUserEndpoint, - BlockDeviceEndpoint: blockDeviceEndpoint, }, nil } diff --git a/platform/api/server/apply/endpoint.go b/platform/api/server/apply/endpoint.go index 69c92cd2..9e97fc45 100644 --- a/platform/api/server/apply/endpoint.go +++ b/platform/api/server/apply/endpoint.go @@ -15,7 +15,6 @@ 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) { @@ -30,17 +29,6 @@ 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, @@ -116,16 +104,6 @@ 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 @@ -168,13 +146,3 @@ 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 } diff --git a/platform/api/server/apply/service.go b/platform/api/server/apply/service.go index 494b5cc3..85b87cbb 100644 --- a/platform/api/server/apply/service.go +++ b/platform/api/server/apply/service.go @@ -19,7 +19,6 @@ import ( "github.com/micromdm/micromdm/platform/appstore" "github.com/micromdm/micromdm/platform/deptoken" "github.com/micromdm/micromdm/platform/pubsub" - "github.com/micromdm/micromdm/platform/remove" "github.com/micromdm/micromdm/platform/user" ) @@ -28,7 +27,6 @@ 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 { @@ -38,7 +36,6 @@ 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) { diff --git a/platform/api/server/apply/transport_http.go b/platform/api/server/apply/transport_http.go index 764f9ef0..f56ed6ae 100644 --- a/platform/api/server/apply/transport_http.go +++ b/platform/api/server/apply/transport_http.go @@ -8,10 +8,8 @@ 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,7 +18,6 @@ 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 { @@ -49,28 +46,10 @@ 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 { @@ -199,13 +178,6 @@ 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 DecodeDEPTokensResponse(_ context.Context, r *http.Response) (interface{}, error) { if r.StatusCode != http.StatusOK { return nil, errorDecoder(r) @@ -241,12 +213,3 @@ 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 -} diff --git a/platform/api/server/remove/endpoint.go b/platform/api/server/remove/endpoint.go deleted file mode 100644 index 383b014a..00000000 --- a/platform/api/server/remove/endpoint.go +++ /dev/null @@ -1,67 +0,0 @@ -package remove - -import ( - "context" - - "github.com/go-kit/kit/endpoint" -) - -type Endpoints struct { - UnblockDeviceEndpoint endpoint.Endpoint -} - -func MakeEndpoints(svc Service) Endpoints { - e := Endpoints{ - 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 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"` -} - -type blueprintResponse struct { - Err error `json:"err,omitempty"` -} - -func (r blueprintResponse) error() error { return r.Err } - -type profileRequest struct { - Identifiers []string `json:"ids"` -} - -type profileResponse struct { - Err error `json:"err,omitempty"` -} - -func (r profileResponse) error() error { return r.Err } diff --git a/platform/api/server/remove/service.go b/platform/api/server/remove/service.go deleted file mode 100644 index b0db3193..00000000 --- a/platform/api/server/remove/service.go +++ /dev/null @@ -1,15 +0,0 @@ -package remove - -import ( - "context" - - "github.com/micromdm/micromdm/platform/remove" -) - -type Service interface { - UnblockDevice(ctx context.Context, udid string) error -} - -type RemoveService struct { - *remove.RemoveService -} diff --git a/platform/api/server/remove/transport_http.go b/platform/api/server/remove/transport_http.go deleted file mode 100644 index 58054b79..00000000 --- a/platform/api/server/remove/transport_http.go +++ /dev/null @@ -1,103 +0,0 @@ -package remove - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "net/url" - - httptransport "github.com/go-kit/kit/transport/http" - "github.com/gorilla/mux" -) - -type HTTPHandlers struct { - UnblockDeviceHandler http.Handler -} - -func MakeHTTPHandlers(ctx context.Context, endpoint Endpoints, opts ...httptransport.ServerOption) HTTPHandlers { - h := HTTPHandlers{ - UnblockDeviceHandler: httptransport.NewServer( - endpoint.UnblockDeviceEndpoint, - decodeUnblockDeviceRequest, - encodeResponse, - opts..., - ), - } - return h -} - -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"` -} - -type errorer interface { - error() error -} - -func errorDecoder(r *http.Response) error { - var w errorWrapper - if err := json.NewDecoder(r.Body).Decode(&w); err != nil { - return err - } - return errors.New(w.Error) -} - -func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { - if e, ok := response.(errorer); ok && e.error() != nil { - EncodeError(ctx, e.error(), w) - return nil - } - - enc := json.NewEncoder(w) - enc.SetIndent("", " ") - return enc.Encode(response) -} - -func EncodeError(ctx context.Context, err error, w http.ResponseWriter) { - w.WriteHeader(http.StatusInternalServerError) - enc := json.NewEncoder(w) - enc.SetIndent("", " ") - enc.Encode(errorWrapper{Error: err.Error()}) -} - -// EncodeHTTPGenericRequest is a transport/http.EncodeRequestFunc that -// JSON-encodes any request to the request body. Primarily useful in a client. -func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interface{}) error { - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(request); err != nil { - return err - } - r.Body = ioutil.NopCloser(&buf) - 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 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 -} diff --git a/platform/remove/block_device.go b/platform/remove/block_device.go new file mode 100644 index 00000000..d103995d --- /dev/null +++ b/platform/remove/block_device.go @@ -0,0 +1,75 @@ +package remove + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/go-kit/kit/endpoint" + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +func (svc *RemoveService) BlockDevice(ctx context.Context, udid string) error { + return svc.store.Save(&Device{UDID: udid}) +} + +type blockDeviceRequest struct { + UDID string +} + +type blockDeviceResponse struct { + Err error `json:"err,omitempty"` +} + +func (r blockDeviceResponse) error() error { return r.Err } + +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 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 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 +} + +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 + } +} + +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 +} diff --git a/platform/remove/db.go b/platform/remove/builtin/db.go similarity index 83% rename from platform/remove/db.go rename to platform/remove/builtin/db.go index 3f22128b..8035f6a8 100644 --- a/platform/remove/db.go +++ b/platform/remove/builtin/db.go @@ -1,9 +1,10 @@ -package remove +package builtin import ( "fmt" "github.com/boltdb/bolt" + "github.com/micromdm/micromdm/platform/remove" "github.com/pkg/errors" ) @@ -27,20 +28,20 @@ func NewDB(db *bolt.DB) (*DB, error) { return datastore, nil } -func (db *DB) DeviceByUDID(udid string) (*Device, error) { - var dev Device +func (db *DB) DeviceByUDID(udid string) (*remove.Device, error) { + var dev remove.Device err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(RemoveBucket)) v := b.Get([]byte(udid)) if v == nil { return ¬Found{"Device", fmt.Sprintf("udid %s", udid)} } - return UnmarshalDevice(v, &dev) + return remove.UnmarshalDevice(v, &dev) }) return &dev, errors.Wrap(err, "remove: get device by udid") } -func (db *DB) Save(dev *Device) error { +func (db *DB) Save(dev *remove.Device) error { tx, err := db.DB.Begin(true) if err != nil { return errors.Wrap(err, "begin transaction") @@ -49,7 +50,7 @@ func (db *DB) Save(dev *Device) error { if bkt == nil { return fmt.Errorf("bucket %q not found!", RemoveBucket) } - pb, err := MarshalDevice(dev) + pb, err := remove.MarshalDevice(dev) if err != nil { return errors.Wrap(err, "marshalling Device") } @@ -81,10 +82,6 @@ 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 +func (e *notFound) NotFound() bool { + return true } diff --git a/platform/api/server/remove/client.go b/platform/remove/client.go similarity index 65% rename from platform/api/server/remove/client.go rename to platform/remove/client.go index c8c389a5..cc5cd46f 100644 --- a/platform/api/server/remove/client.go +++ b/platform/remove/client.go @@ -10,24 +10,36 @@ import ( httptransport "github.com/go-kit/kit/transport/http" ) -func NewClient(instance string, logger log.Logger, token string, opts ...httptransport.ClientOption) (Service, error) { +func NewHTTPClient(instance, token string, logger log.Logger, opts ...httptransport.ClientOption) (Service, error) { u, err := url.Parse(instance) if err != nil { return nil, err } + var blockDeviceEndpoint endpoint.Endpoint + { + blockDeviceEndpoint = httptransport.NewClient( + "POST", + copyURL(u, ""), // empty path, modified by the encodeRequest func + encodeRequestWithToken(token, encodeBlockDeviceRequest), + decodeBlockDeviceResponse, + opts..., + ).Endpoint() + } + var unblockDeviceEndpoint endpoint.Endpoint { unblockDeviceEndpoint = httptransport.NewClient( "POST", copyURL(u, ""), //modified by encodeRequestFunc encodeRequestWithToken(token, encodeUnblockDeviceRequest), - DecodeUnblockDeviceResponse, + decodeUnblockDeviceResponse, opts..., ).Endpoint() } return Endpoints{ + BlockDeviceEndpoint: blockDeviceEndpoint, UnblockDeviceEndpoint: unblockDeviceEndpoint, }, nil } diff --git a/platform/remove/remove.go b/platform/remove/remove.go index ba1a3ab6..1197c975 100644 --- a/platform/remove/remove.go +++ b/platform/remove/remove.go @@ -10,27 +10,6 @@ import ( "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"` } @@ -51,23 +30,23 @@ func UnmarshalDevice(data []byte, dev *Device) error { return nil } -func RemoveMiddleware(db *DB) connect.Middleware { +func RemoveMiddleware(store Store) connect.Middleware { return func(next connect.Service) connect.Service { return &removeMiddleware{ - db: db, - next: next, + store: store, + next: next, } } } type removeMiddleware struct { - db *DB - next connect.Service + store Store + next connect.Service } func (mw removeMiddleware) Acknowledge(ctx context.Context, req connect.MDMConnectRequest) ([]byte, error) { udid := req.MDMResponse.UDID - _, err := mw.db.DeviceByUDID(udid) + _, err := mw.store.DeviceByUDID(udid) if err != nil { if !isNotFound(err) { return nil, errors.Wrapf(err, "remove: get device by udid %s", udid) @@ -88,3 +67,13 @@ func (checkoutErr) Error() string { func (checkoutErr) Checkout() bool { return true } + +func isNotFound(err error) bool { + type notFoundError interface { + error + NotFound() bool + } + + _, ok := err.(notFoundError) + return ok +} diff --git a/platform/remove/server.go b/platform/remove/server.go new file mode 100644 index 00000000..4bb82ef4 --- /dev/null +++ b/platform/remove/server.go @@ -0,0 +1,68 @@ +package remove + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/log" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/gorilla/mux" +) + +type Endpoints struct { + BlockDeviceEndpoint endpoint.Endpoint + UnblockDeviceEndpoint endpoint.Endpoint +} + +func MakeServerEndpoints(s Service) Endpoints { + return Endpoints{ + BlockDeviceEndpoint: MakeBlockDeviceEndpoint(s), + UnblockDeviceEndpoint: MakeUnblockDeviceEndpoint(s), + } +} + +func MakeHTTPHandler(e Endpoints, logger log.Logger) http.Handler { + options := []httptransport.ServerOption{ + httptransport.ServerErrorLogger(logger), + } + + r := mux.NewRouter() + + // POST /v1/devices/:udid/block force a device to unenroll next time it connects + // POST /v1/devices/:udid/unblock allow a blocked device to enroll again + + r.Methods("POST").Path("/v1/devices/{udid}/block").Handler(httptransport.NewServer( + e.BlockDeviceEndpoint, + decodeBlockDeviceRequest, + httptransport.EncodeJSONResponse, + options..., + )) + + r.Methods("POST").Path("/v1/devices/{udid}/unblock").Handler(httptransport.NewServer( + e.UnblockDeviceEndpoint, + decodeUnblockDeviceRequest, + httptransport.EncodeJSONResponse, + options..., + )) + + return r + +} + +type errorWrapper struct { + Error string `json:"error"` +} + +type errorer interface { + error() error +} + +func errorDecoder(r *http.Response) error { + var w errorWrapper + if err := json.NewDecoder(r.Body).Decode(&w); err != nil { + return err + } + return errors.New(w.Error) +} diff --git a/platform/remove/service.go b/platform/remove/service.go new file mode 100644 index 00000000..61802782 --- /dev/null +++ b/platform/remove/service.go @@ -0,0 +1,22 @@ +package remove + +import "context" + +type Service interface { + BlockDevice(ctx context.Context, udid string) error + UnblockDevice(ctx context.Context, udid string) error +} + +type Store interface { + Save(*Device) error + DeviceByUDID(string) (*Device, error) + Delete(string) error +} + +type RemoveService struct { + store Store +} + +func New(store Store) (*RemoveService, error) { + return &RemoveService{store: store}, nil +} diff --git a/platform/remove/unblock_device.go b/platform/remove/unblock_device.go new file mode 100644 index 00000000..73b53135 --- /dev/null +++ b/platform/remove/unblock_device.go @@ -0,0 +1,73 @@ +package remove + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/url" + + "github.com/go-kit/kit/endpoint" + "github.com/gorilla/mux" +) + +func (svc *RemoveService) UnblockDevice(ctx context.Context, udid string) error { + return svc.store.Delete(udid) +} + +type unblockDeviceRequest struct { + UDID string +} + +type unblockDeviceResponse struct { + Err error `json:"err,omitempty"` +} + +func (r unblockDeviceResponse) error() error { return r.Err } + +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 +} + +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 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 +} + +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 + } +} + +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 +}